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