1 /*
2  * Copyright (C) 2023 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.example.android.vdmdemo.host;
18 
19 import android.graphics.PointF;
20 import android.hardware.Sensor;
21 import android.hardware.SensorEvent;
22 import android.hardware.SensorEventListener;
23 import android.hardware.SensorManager;
24 import android.os.Bundle;
25 import android.view.GestureDetector;
26 import android.view.MotionEvent;
27 import android.view.View;
28 import android.widget.TextView;
29 
30 import androidx.annotation.NonNull;
31 import androidx.annotation.StringRes;
32 import androidx.core.view.GestureDetectorCompat;
33 import androidx.fragment.app.Fragment;
34 
35 import dagger.hilt.android.AndroidEntryPoint;
36 
37 import javax.inject.Inject;
38 
39 /** VDM Host Input fragment for mouse event injection. */
40 @AndroidEntryPoint(Fragment.class)
41 public abstract class MouseFragment extends Hilt_MouseFragment {
42 
43     @Inject
44     InputController mInputController;
45 
46     private GestureDetectorCompat mDetector;
47 
48     protected GestureListener mGestureListener = new GestureListener();
49     protected int mNumFingers = 0;
50 
51 
MouseFragment()52     public MouseFragment() {
53         super(R.layout.fragment_input_mouse);
54     }
55 
56     @Override
onViewCreated(View view, Bundle savedInstanceState)57     public void onViewCreated(View view, Bundle savedInstanceState) {
58         mDetector = new GestureDetectorCompat(requireContext(), mGestureListener);
59         TextView touchpad = view.requireViewById(R.id.touchpad);
60         touchpad.setText(getViewTextResourceId());
61         touchpad.setOnTouchListener((v, e) -> {
62             mNumFingers = e.getPointerCount();
63             return mDetector.onTouchEvent(e);
64         });
65     }
66 
getViewTextResourceId()67     protected abstract @StringRes int getViewTextResourceId();
68 
69     protected class GestureListener extends GestureDetector.SimpleOnGestureListener {
70 
71         private static final float SCROLL_THRESHOLD = 80f;
72         private float mScrollX = 0;
73         private float mScrollY = 0;
74 
75         @Override
onDown(@onNull MotionEvent e)76         public boolean onDown(@NonNull MotionEvent e) {
77             mScrollX = mScrollY = 0;
78             return true;
79         }
80 
81         @Override
onSingleTapUp(@onNull MotionEvent e)82         public boolean onSingleTapUp(@NonNull MotionEvent e) {
83             mInputController.sendMouseButtonEvent(MotionEvent.BUTTON_PRIMARY);
84             return true;
85         }
86 
87         @Override
onLongPress(@onNull MotionEvent e)88         public void onLongPress(@NonNull MotionEvent e) {
89             mInputController.sendMouseButtonEvent(MotionEvent.BUTTON_SECONDARY);
90         }
91 
92         @Override
onScroll( MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY)93         public boolean onScroll(
94                 MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY) {
95             mScrollX += distanceX;
96             mScrollY += distanceY;
97             if (Math.abs(mScrollX) > SCROLL_THRESHOLD && Math.abs(distanceY) > SCROLL_THRESHOLD) {
98                 mInputController.sendMouseScrollEvent(mScrollX, mScrollY);
99                 mScrollX = mScrollY = 0;
100             } else if (Math.abs(mScrollX) > SCROLL_THRESHOLD) {
101                 mInputController.sendMouseScrollEvent(mScrollX, 0);
102                 mScrollX = 0;
103             } else if (Math.abs(mScrollY) > SCROLL_THRESHOLD) {
104                 mInputController.sendMouseScrollEvent(0, mScrollY);
105                 mScrollY = 0;
106             }
107             return true;
108         }
109     }
110 
111     @AndroidEntryPoint(MouseFragment.class)
112     public static final class TouchpadFragment extends Hilt_MouseFragment_TouchpadFragment {
113 
TouchpadFragment()114         public TouchpadFragment() {
115             mGestureListener = new GestureListener() {
116                 @Override
117                 public boolean onScroll(
118                         MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY) {
119                     if (mNumFingers == 1) {
120                         mInputController.sendMouseRelativeEvent(-distanceX, -distanceY);
121                     } else if (mNumFingers == 2) {
122                         return super.onScroll(e1, e2, distanceX, distanceY);
123                     }
124                     return true;
125                 }
126             };
127         }
128 
129         @Override
getViewTextResourceId()130         protected @StringRes int getViewTextResourceId() {
131             return R.string.touchpad_label;
132         }
133     }
134 
135 
136     @AndroidEntryPoint(MouseFragment.class)
137     public static final class RemoteFragment extends Hilt_MouseFragment_RemoteFragment {
138 
139         private static final float SENSOR_EVENT_THRESHOLD = 0.04f;
140         private static final float SENSOR_EVENT_SCALE = 0.025f;
141 
142         private SensorManager mSensorManager;
143 
144         private final SensorEventListener mSensorEventListener = new SensorEventListener() {
145             @Override
146             public void onSensorChanged(SensorEvent event) {
147                 float x = -event.values[2];
148                 float y = -event.values[0];
149                 PointF displaySize =
150                         mInputController.getFocusedDisplaySize().orElse(new PointF(0, 0));
151                 if (Math.abs(x) > SENSOR_EVENT_THRESHOLD && Math.abs(y) > SENSOR_EVENT_THRESHOLD) {
152                     x *= SENSOR_EVENT_SCALE * displaySize.x;
153                     y *= SENSOR_EVENT_SCALE * displaySize.y;
154                     mInputController.sendMouseRelativeEvent(x, y);
155                 }
156             }
157 
158             @Override
159             public void onAccuracyChanged(Sensor sensor, int accuracy) {}
160         };
161 
162         @Override
onResume()163         public void onResume() {
164             super.onResume();
165             mSensorManager = requireContext().getSystemService(SensorManager.class);
166             mSensorManager.registerListener(
167                     mSensorEventListener,
168                     mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE),
169                     SensorManager.SENSOR_DELAY_GAME);
170         }
171 
172         @Override
onPause()173         public void onPause() {
174             super.onPause();
175             mSensorManager.unregisterListener(mSensorEventListener);
176         }
177 
178         @Override
getViewTextResourceId()179         protected @StringRes int getViewTextResourceId() {
180             return R.string.remote_control_label;
181         }
182     }
183 }
184