1 /* 2 * Copyright (C) 2022 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.google.android.car.kitchensink.key; 18 19 import android.car.CarOccupantZoneManager; 20 import android.car.CarOccupantZoneManager.OccupantZoneInfo; 21 import android.content.Context; 22 import android.hardware.display.DisplayManager; 23 import android.os.Bundle; 24 import android.os.Handler; 25 import android.os.Process; 26 import android.util.Log; 27 import android.view.Display; 28 import android.view.KeyEvent; 29 import android.view.LayoutInflater; 30 import android.view.View; 31 import android.view.ViewGroup; 32 import android.widget.AdapterView; 33 import android.widget.ArrayAdapter; 34 import android.widget.Button; 35 import android.widget.EditText; 36 import android.widget.Spinner; 37 38 import androidx.annotation.NonNull; 39 import androidx.annotation.Nullable; 40 import androidx.fragment.app.Fragment; 41 import androidx.recyclerview.widget.GridLayoutManager; 42 import androidx.recyclerview.widget.RecyclerView; 43 44 import com.google.android.car.kitchensink.KitchenSinkActivity; 45 import com.google.android.car.kitchensink.R; 46 import com.google.android.car.kitchensink.util.InjectKeyEventUtils; 47 48 import java.util.ArrayList; 49 import java.util.List; 50 51 public final class InjectKeyTestFragment extends Fragment { 52 private static final String TAG = InjectKeyTestFragment.class.getSimpleName(); 53 54 private static final int KEY_NUM_COL = 3; 55 56 private static final int[] KEY_CODES = { 57 KeyEvent.KEYCODE_BACK, 58 KeyEvent.KEYCODE_HOME, 59 KeyEvent.KEYCODE_POWER, 60 KeyEvent.KEYCODE_VOLUME_UP, 61 KeyEvent.KEYCODE_VOLUME_DOWN, 62 KeyEvent.KEYCODE_VOLUME_MUTE, 63 KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 64 KeyEvent.KEYCODE_MEDIA_PREVIOUS, 65 KeyEvent.KEYCODE_MEDIA_NEXT 66 }; 67 68 private DisplayManager mDisplayManager; 69 private Display mCurrentDisplay; 70 private Display mTargetDisplay; 71 72 private Spinner mDisplaySpinner; 73 private CarOccupantZoneManager mOccupantZoneManager; 74 private RecyclerView mKeysLayout; 75 private EditText mCustomKeyEditText; 76 77 @Override onCreate(@ullable Bundle savedInstanceState)78 public void onCreate(@Nullable Bundle savedInstanceState) { 79 super.onCreate(savedInstanceState); 80 81 mDisplayManager = getContext().getSystemService(DisplayManager.class); 82 mCurrentDisplay = getContext().getDisplay(); 83 84 final Runnable r = () -> { 85 mOccupantZoneManager = ((KitchenSinkActivity) getActivity()).getOccupantZoneManager(); 86 }; 87 ((KitchenSinkActivity) getActivity()).requestRefreshManager(r, 88 new Handler(getContext().getMainLooper())); 89 } 90 91 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle)92 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { 93 View view = inflater.inflate(R.layout.injecting_key_fragment, container, false); 94 mDisplaySpinner = view.findViewById(R.id.display_select_spinner); 95 mKeysLayout = view.findViewById(R.id.key_buttons); 96 mKeysLayout.setAdapter(new KeysAdapter(getContext())); 97 mKeysLayout.setLayoutManager(new GridLayoutManager(getContext(), KEY_NUM_COL)); 98 mCustomKeyEditText = view.findViewById(R.id.custom_key_edit); 99 return view; 100 } 101 102 @Override onViewCreated(@onNull View view, @Nullable Bundle savedInstanceState)103 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 104 super.onViewCreated(view, savedInstanceState); 105 mDisplaySpinner.setAdapter(new ArrayAdapter<>(getContext(), 106 android.R.layout.simple_spinner_item, getDisplays())); 107 mDisplaySpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 108 @Override 109 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 110 int selectedDisplayId = (Integer) mDisplaySpinner.getItemAtPosition(position); 111 mTargetDisplay = mDisplayManager.getDisplay(selectedDisplayId); 112 } 113 114 @Override 115 public void onNothingSelected(AdapterView<?> parent) { 116 // nothing to do 117 } 118 }); 119 view.findViewById(R.id.custom_key_button).setOnClickListener((v) -> injectCustomKey()); 120 } 121 getOccupantZoneForDisplayId(int displayId)122 private OccupantZoneInfo getOccupantZoneForDisplayId(int displayId) { 123 List<OccupantZoneInfo> zones = mOccupantZoneManager.getAllOccupantZones(); 124 for (OccupantZoneInfo zone : zones) { 125 List<Display> displays = mOccupantZoneManager.getAllDisplaysForOccupant(zone); 126 for (Display disp : displays) { 127 if (disp.getDisplayId() == displayId) { 128 return zone; 129 } 130 } 131 } 132 return null; 133 } 134 135 /** 136 * Generates a custom keycode through text input. 137 */ injectCustomKey()138 private void injectCustomKey() { 139 String customKey = mCustomKeyEditText.getText().toString(); 140 if (customKey == null || customKey.length() == 0) { 141 return; 142 } 143 try { 144 injectKeyByShell(Integer.parseInt(customKey)); 145 } catch (NumberFormatException e) { 146 Log.e(TAG, "Invalid key code: " + customKey, e); 147 } 148 } 149 150 injectKeyByShell(int keyCode)151 private void injectKeyByShell(int keyCode) { 152 if (mTargetDisplay == null) { 153 return; 154 } 155 156 OccupantZoneInfo zone = getOccupantZoneForDisplayId( 157 mTargetDisplay.getDisplayId()); 158 InjectKeyEventUtils.injectKeyByShell(zone, keyCode); 159 } 160 getDisplays()161 private ArrayList<Integer> getDisplays() { 162 ArrayList<Integer> displayIds = new ArrayList<>(); 163 Display[] displays = mDisplayManager.getDisplays(); 164 int uidSelf = Process.myUid(); 165 for (Display disp : displays) { 166 if (!disp.hasAccess(uidSelf)) { 167 Log.d(TAG, "Cannot access the display: displayId=" + disp.getDisplayId()); 168 continue; 169 } 170 if (mCurrentDisplay != null && disp.getDisplayId() == mCurrentDisplay.getDisplayId()) { 171 // skip the current display 172 continue; 173 } 174 displayIds.add(disp.getDisplayId()); 175 } 176 return displayIds; 177 } 178 179 private final class KeysAdapter extends RecyclerView.Adapter<ItemViewHolder> { 180 private final LayoutInflater mLayoutInflator; 181 KeysAdapter(Context context)182 KeysAdapter(Context context) { 183 mLayoutInflator = LayoutInflater.from(context); 184 } 185 186 @Override onCreateViewHolder(ViewGroup parent, int viewType)187 public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 188 View view = mLayoutInflator.inflate(R.layout.injecting_key_item, parent, false); 189 return new ItemViewHolder(view); 190 } 191 192 @Override onBindViewHolder(ItemViewHolder holder, int position)193 public void onBindViewHolder(ItemViewHolder holder, int position) { 194 holder.mButton.setText(KeyEvent.keyCodeToString(KEY_CODES[position])); 195 holder.mButton.setOnClickListener(v -> injectKeyByShell(KEY_CODES[position])); 196 } 197 198 @Override getItemCount()199 public int getItemCount() { 200 return KEY_CODES.length; 201 } 202 } 203 204 private final class ItemViewHolder extends RecyclerView.ViewHolder { 205 Button mButton; 206 ItemViewHolder(View itemView)207 ItemViewHolder(View itemView) { 208 super(itemView); 209 mButton = itemView.findViewById(R.id.inject_key_button); 210 } 211 } 212 } 213