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