• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.os.Bundle;
20 import android.view.KeyEvent;
21 import android.view.MotionEvent;
22 import android.view.View;
23 import android.view.inputmethod.InputMethodManager;
24 
25 import androidx.appcompat.app.AppCompatActivity;
26 import androidx.appcompat.widget.Toolbar;
27 import androidx.fragment.app.Fragment;
28 import androidx.preference.PreferenceManager;
29 
30 import com.example.android.vdmdemo.common.DpadFragment;
31 import com.example.android.vdmdemo.common.EdgeToEdgeUtils;
32 import com.example.android.vdmdemo.common.NavTouchpadFragment;
33 import com.example.android.vdmdemo.common.RemoteEventProto.InputDeviceType;
34 import com.example.android.vdmdemo.common.RotaryFragment;
35 import com.google.android.material.bottomnavigation.BottomNavigationView;
36 
37 import dagger.hilt.android.AndroidEntryPoint;
38 
39 import javax.inject.Inject;
40 
41 /** VDM Host Input activity allowing the host device to become an input device for the client. */
42 @AndroidEntryPoint(AppCompatActivity.class)
43 public class InputActivity extends Hilt_InputActivity {
44 
45     private static final String TAG = "VdmInputActivity";
46 
47     @Inject InputController mInputController;
48     @Inject PreferenceController mPreferenceController;
49 
50     boolean mOriginalShowPointerIconPreference;
51 
52     @Override
onCreate(Bundle savedInstanceState)53     public void onCreate(Bundle savedInstanceState) {
54         super.onCreate(savedInstanceState);
55 
56         setContentView(R.layout.activity_input);
57         Toolbar toolbar = requireViewById(R.id.main_tool_bar);
58         setSupportActionBar(toolbar);
59         toolbar.setNavigationOnClickListener(v -> finish());
60         setTitle(getTitle() + " " + getString(R.string.input));
61         EdgeToEdgeUtils.applyTopInsets(toolbar);
62 
63         mOriginalShowPointerIconPreference =
64                 mPreferenceController.getBoolean(R.string.pref_show_pointer_icon);
65 
66         Fragment touchpadFragment = new MouseFragment.TouchpadFragment();
67         Fragment remoteFragment = new MouseFragment.RemoteFragment();
68         Fragment navigationFragment = new NavigationFragment();
69         Fragment stylusFragment = new StylusFragment();
70         Fragment keyboardFragment = new Fragment();
71 
72         BottomNavigationView bottomNavigationView = requireViewById(R.id.bottom_nav);
73         bottomNavigationView.setOnItemSelectedListener(item -> {
74             Fragment fragment;
75             switch (item.getItemId()) {
76                 case R.id.mouse -> {
77                     setShowPointerIcon(true);
78                     fragment = touchpadFragment;
79                 }
80                 case R.id.remote -> {
81                     setShowPointerIcon(true);
82                     fragment = remoteFragment;
83                 }
84                 case R.id.navigation -> {
85                     setShowPointerIcon(mOriginalShowPointerIconPreference);
86                     fragment = navigationFragment;
87                 }
88                 case R.id.stylus -> {
89                     setShowPointerIcon(mOriginalShowPointerIconPreference);
90                     fragment = stylusFragment;
91                 }
92                 case R.id.keyboard -> {
93                     setShowPointerIcon(mOriginalShowPointerIconPreference);
94                     fragment = keyboardFragment;
95                     getSystemService(InputMethodManager.class)
96                             .showSoftInput(getWindow().getDecorView(), 0);
97                 }
98                 default -> {
99                     return false;
100                 }
101             }
102             getSupportFragmentManager()
103                     .beginTransaction()
104                     .replace(R.id.input_fragment_container_view, fragment)
105                     .commit();
106             return true;
107         });
108 
109         if (!mPreferenceController.getBoolean(R.string.internal_pref_virtual_stylus_supported)) {
110             requireViewById(R.id.stylus).setVisibility(View.GONE);
111         }
112         bottomNavigationView.setSelectedItemId(R.id.mouse);
113 
114         requireViewById(R.id.button_back).setOnClickListener(
115                 v -> mInputController.sendMouseButtonEvent(MotionEvent.BUTTON_BACK));
116         requireViewById(R.id.button_forward).setOnClickListener(
117                 v -> mInputController.sendMouseButtonEvent(MotionEvent.BUTTON_FORWARD));
118         requireViewById(R.id.button_home).setOnClickListener(
119                 v -> mInputController.sendHomeToFocusedDisplay());
120 
121         EdgeToEdgeUtils.applyBottomInsets(bottomNavigationView);
122     }
123 
setShowPointerIcon(boolean show)124     private void setShowPointerIcon(boolean show) {
125         PreferenceManager.getDefaultSharedPreferences(this)
126                 .edit()
127                 .putBoolean(getString(R.string.pref_show_pointer_icon), show)
128                 .commit();
129     }
130 
131     @Override
onDestroy()132     public void onDestroy() {
133         setShowPointerIcon(mOriginalShowPointerIconPreference);
134         super.onDestroy();
135     }
136 
137     @Override
dispatchKeyEvent(KeyEvent event)138     public boolean dispatchKeyEvent(KeyEvent event) {
139         if ((event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) == 0) {
140             return super.dispatchKeyEvent(event);
141         }
142         if (event.getAction() == KeyEvent.ACTION_UP
143                 && (event.getFlags() & KeyEvent.FLAG_CANCELED) == KeyEvent.FLAG_CANCELED) {
144             // Sending key events to another display makes that display top focused, so the key
145             // events sent to the activity here get dropped if not terminal.
146             // DisplayManager.VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED would solve this but it
147             // has other limitations.
148             mInputController.sendEventToFocusedDisplay(
149                     InputDeviceType.DEVICE_TYPE_KEYBOARD,
150                     new KeyEvent(
151                             event.getDownTime(),
152                             event.getEventTime(),
153                             KeyEvent.ACTION_DOWN,
154                             event.getKeyCode(),
155                             /* repeat= */ 0));
156         }
157         mInputController.sendEventToFocusedDisplay(InputDeviceType.DEVICE_TYPE_KEYBOARD, event);
158         return true;
159     }
160 
161 
162     @AndroidEntryPoint(Fragment.class)
163     public static final class NavigationFragment extends Hilt_InputActivity_NavigationFragment {
164 
165         @Inject InputController mInputController;
166         @Inject PreferenceController mPreferenceController;
167 
NavigationFragment()168         public NavigationFragment() {
169             super(R.layout.fragment_input_navigation);
170         }
171 
172         @Override
onViewCreated(View view, Bundle savedInstanceState)173         public void onViewCreated(View view, Bundle savedInstanceState) {
174             DpadFragment dpadFragment =
175                     (DpadFragment) getChildFragmentManager().findFragmentById(
176                             R.id.dpad_fragment_container);
177             dpadFragment.setInputEventListener((event) ->
178                     mInputController.sendEventToFocusedDisplay(
179                             InputDeviceType.DEVICE_TYPE_DPAD, event));
180             NavTouchpadFragment navTouchpadFragment =
181                     (NavTouchpadFragment) getChildFragmentManager().findFragmentById(
182                             R.id.nav_touchpad_fragment_container);
183             navTouchpadFragment.setInputEventListener((event) ->
184                     mInputController.sendEventToFocusedDisplay(
185                             InputDeviceType.DEVICE_TYPE_NAVIGATION_TOUCHPAD, event));
186             RotaryFragment rotaryFragment =
187                     (RotaryFragment) getChildFragmentManager().findFragmentById(
188                             R.id.rotary_fragment_container);
189             if (mPreferenceController.getBoolean(R.string.internal_pref_virtual_rotary_supported)) {
190                 rotaryFragment.setInputEventListener((event) ->
191                         mInputController.sendEventToFocusedDisplay(
192                                 InputDeviceType.DEVICE_TYPE_ROTARY_ENCODER, event));
193             } else {
194                 getChildFragmentManager().beginTransaction().remove(rotaryFragment).commit();
195             }
196         }
197     }
198 }
199