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