1 /*
2  * Copyright (C) 2011 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.apis.view;
18 
19 import com.example.android.apis.R;
20 
21 import android.app.Activity;
22 import android.content.Context;
23 import android.content.res.Resources;
24 import android.hardware.input.InputManager;
25 import android.os.Bundle;
26 import android.util.Log;
27 import android.util.SparseArray;
28 import android.util.SparseIntArray;
29 import android.view.InputDevice;
30 import android.view.KeyEvent;
31 import android.view.LayoutInflater;
32 import android.view.MotionEvent;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.view.InputDevice.MotionRange;
36 import android.widget.AdapterView;
37 import android.widget.BaseAdapter;
38 import android.widget.ListView;
39 import android.widget.TextView;
40 import android.widget.Toast;
41 
42 import java.util.ArrayList;
43 import java.util.List;
44 
45 
46 /**
47  * Demonstrates how to process input events received from game controllers.
48  * It also shows how to detect when input devices are added, removed or reconfigured.
49  *
50  * This activity displays button states and joystick positions.
51  * Also writes detailed information about relevant input events to the log.
52  *
53  * The game controller is also uses to control a very simple game.  See {@link GameView}
54  * for the game itself.
55  */
56 public class GameControllerInput extends Activity
57         implements InputManager.InputDeviceListener {
58     private static final String TAG = "GameControllerInput";
59 
60     private InputManager mInputManager;
61     private SparseArray<InputDeviceState> mInputDeviceStates;
62     private GameView mGame;
63     private ListView mSummaryList;
64     private SummaryAdapter mSummaryAdapter;
65 
66     @Override
onCreate(Bundle savedInstanceState)67     protected void onCreate(Bundle savedInstanceState) {
68         super.onCreate(savedInstanceState);
69 
70         mInputManager = (InputManager)getSystemService(Context.INPUT_SERVICE);
71 
72         mInputDeviceStates = new SparseArray<InputDeviceState>();
73         mSummaryAdapter = new SummaryAdapter(this, getResources());
74 
75         setContentView(R.layout.game_controller_input);
76 
77         mGame = (GameView) findViewById(R.id.game);
78 
79         mSummaryList = (ListView) findViewById(R.id.summary);
80         mSummaryList.setAdapter(mSummaryAdapter);
81         mSummaryList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
82             @Override
83             public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
84                 mSummaryAdapter.onItemClick(position);
85             }
86         });
87     }
88 
89     @Override
onResume()90     protected void onResume() {
91         super.onResume();
92 
93         // Register an input device listener to watch when input devices are
94         // added, removed or reconfigured.
95         mInputManager.registerInputDeviceListener(this, null);
96 
97         // Query all input devices.
98         // We do this so that we can see them in the log as they are enumerated.
99         int[] ids = mInputManager.getInputDeviceIds();
100         for (int i = 0; i < ids.length; i++) {
101             getInputDeviceState(ids[i]);
102         }
103     }
104 
105     @Override
onPause()106     protected void onPause() {
107         super.onPause();
108 
109         // Remove the input device listener when the activity is paused.
110         mInputManager.unregisterInputDeviceListener(this);
111     }
112 
113     @Override
onWindowFocusChanged(boolean hasFocus)114     public void onWindowFocusChanged(boolean hasFocus) {
115         super.onWindowFocusChanged(hasFocus);
116 
117         mGame.requestFocus();
118     }
119 
120     @Override
dispatchKeyEvent(KeyEvent event)121     public boolean dispatchKeyEvent(KeyEvent event) {
122         // Update device state for visualization and logging.
123         InputDeviceState state = getInputDeviceState(event.getDeviceId());
124         if (state != null) {
125             switch (event.getAction()) {
126                 case KeyEvent.ACTION_DOWN:
127                     if (state.onKeyDown(event)) {
128                         mSummaryAdapter.show(state);
129                     }
130                     break;
131                 case KeyEvent.ACTION_UP:
132                     if (state.onKeyUp(event)) {
133                         mSummaryAdapter.show(state);
134                     }
135                     break;
136             }
137         }
138         return super.dispatchKeyEvent(event);
139     }
140 
141     @Override
dispatchGenericMotionEvent(MotionEvent event)142     public boolean dispatchGenericMotionEvent(MotionEvent event) {
143         // Check that the event came from a joystick since a generic motion event
144         // could be almost anything.
145         if (event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK)
146                 && event.getAction() == MotionEvent.ACTION_MOVE) {
147             // Update device state for visualization and logging.
148             InputDeviceState state = getInputDeviceState(event.getDeviceId());
149             if (state != null && state.onJoystickMotion(event)) {
150                 mSummaryAdapter.show(state);
151             }
152         }
153         return super.dispatchGenericMotionEvent(event);
154     }
155 
getInputDeviceState(int deviceId)156     private InputDeviceState getInputDeviceState(int deviceId) {
157         InputDeviceState state = mInputDeviceStates.get(deviceId);
158         if (state == null) {
159             final InputDevice device = mInputManager.getInputDevice(deviceId);
160             if (device == null) {
161                 return null;
162             }
163             state = new InputDeviceState(device);
164             mInputDeviceStates.put(deviceId, state);
165             Log.i(TAG, "Device enumerated: " + state.mDevice);
166         }
167         return state;
168     }
169 
170     // Implementation of InputManager.InputDeviceListener.onInputDeviceAdded()
171     @Override
onInputDeviceAdded(int deviceId)172     public void onInputDeviceAdded(int deviceId) {
173         InputDeviceState state = getInputDeviceState(deviceId);
174         Log.i(TAG, "Device added: " + state.mDevice);
175     }
176 
177     // Implementation of InputManager.InputDeviceListener.onInputDeviceChanged()
178     @Override
onInputDeviceChanged(int deviceId)179     public void onInputDeviceChanged(int deviceId) {
180         InputDeviceState state = mInputDeviceStates.get(deviceId);
181         if (state != null) {
182             mInputDeviceStates.remove(deviceId);
183             state = getInputDeviceState(deviceId);
184             Log.i(TAG, "Device changed: " + state.mDevice);
185         }
186     }
187 
188     // Implementation of InputManager.InputDeviceListener.onInputDeviceRemoved()
189     @Override
onInputDeviceRemoved(int deviceId)190     public void onInputDeviceRemoved(int deviceId) {
191         InputDeviceState state = mInputDeviceStates.get(deviceId);
192         if (state != null) {
193             Log.i(TAG, "Device removed: " + state.mDevice);
194             mInputDeviceStates.remove(deviceId);
195         }
196     }
197 
198     /**
199      * Tracks the state of joystick axes and game controller buttons for a particular
200      * input device for diagnostic purposes.
201      */
202     private static class InputDeviceState {
203         private final InputDevice mDevice;
204         private final int[] mAxes;
205         private final float[] mAxisValues;
206         private final SparseIntArray mKeys;
207 
InputDeviceState(InputDevice device)208         public InputDeviceState(InputDevice device) {
209             mDevice = device;
210 
211             int numAxes = 0;
212             final List<MotionRange> ranges = device.getMotionRanges();
213             for (MotionRange range : ranges) {
214                 if (range.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK)) {
215                     numAxes += 1;
216                 }
217             }
218 
219             mAxes = new int[numAxes];
220             mAxisValues = new float[numAxes];
221             int i = 0;
222             for (MotionRange range : ranges) {
223                 if (range.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK)) {
224                     mAxes[i++] = range.getAxis();
225                 }
226             }
227 
228             mKeys = new SparseIntArray();
229         }
230 
getDevice()231         public InputDevice getDevice() {
232             return mDevice;
233         }
234 
getAxisCount()235         public int getAxisCount() {
236             return mAxes.length;
237         }
238 
getAxis(int axisIndex)239         public int getAxis(int axisIndex) {
240             return mAxes[axisIndex];
241         }
242 
getAxisValue(int axisIndex)243         public float getAxisValue(int axisIndex) {
244             return mAxisValues[axisIndex];
245         }
246 
getKeyCount()247         public int getKeyCount() {
248             return mKeys.size();
249         }
250 
getKeyCode(int keyIndex)251         public int getKeyCode(int keyIndex) {
252             return mKeys.keyAt(keyIndex);
253         }
254 
isKeyPressed(int keyIndex)255         public boolean isKeyPressed(int keyIndex) {
256             return mKeys.valueAt(keyIndex) != 0;
257         }
258 
onKeyDown(KeyEvent event)259         public boolean onKeyDown(KeyEvent event) {
260             final int keyCode = event.getKeyCode();
261             if (isGameKey(keyCode)) {
262                 if (event.getRepeatCount() == 0) {
263                     final String symbolicName = KeyEvent.keyCodeToString(keyCode);
264                     mKeys.put(keyCode, 1);
265                     Log.i(TAG, mDevice.getName() + " - Key Down: " + symbolicName);
266                 }
267                 return true;
268             }
269             return false;
270         }
271 
onKeyUp(KeyEvent event)272         public boolean onKeyUp(KeyEvent event) {
273             final int keyCode = event.getKeyCode();
274             if (isGameKey(keyCode)) {
275                 int index = mKeys.indexOfKey(keyCode);
276                 if (index >= 0) {
277                     final String symbolicName = KeyEvent.keyCodeToString(keyCode);
278                     mKeys.put(keyCode, 0);
279                     Log.i(TAG, mDevice.getName() + " - Key Up: " + symbolicName);
280                 }
281                 return true;
282             }
283             return false;
284         }
285 
onJoystickMotion(MotionEvent event)286         public boolean onJoystickMotion(MotionEvent event) {
287             StringBuilder message = new StringBuilder();
288             message.append(mDevice.getName()).append(" - Joystick Motion:\n");
289 
290             final int historySize = event.getHistorySize();
291             for (int i = 0; i < mAxes.length; i++) {
292                 final int axis = mAxes[i];
293                 final float value = event.getAxisValue(axis);
294                 mAxisValues[i] = value;
295                 message.append("  ").append(MotionEvent.axisToString(axis)).append(": ");
296 
297                 // Append all historical values in the batch.
298                 for (int historyPos = 0; historyPos < historySize; historyPos++) {
299                     message.append(event.getHistoricalAxisValue(axis, historyPos));
300                     message.append(", ");
301                 }
302 
303                 // Append the current value.
304                 message.append(value);
305                 message.append("\n");
306             }
307             Log.i(TAG, message.toString());
308             return true;
309         }
310 
311         // Check whether this is a key we care about.
312         // In a real game, we would probably let the user configure which keys to use
313         // instead of hardcoding the keys like this.
isGameKey(int keyCode)314         private static boolean isGameKey(int keyCode) {
315             switch (keyCode) {
316                 case KeyEvent.KEYCODE_DPAD_UP:
317                 case KeyEvent.KEYCODE_DPAD_DOWN:
318                 case KeyEvent.KEYCODE_DPAD_LEFT:
319                 case KeyEvent.KEYCODE_DPAD_RIGHT:
320                 case KeyEvent.KEYCODE_DPAD_CENTER:
321                 case KeyEvent.KEYCODE_SPACE:
322                     return true;
323                 default:
324                     return KeyEvent.isGamepadButton(keyCode);
325             }
326         }
327     }
328 
329     /**
330      * A list adapter that displays a summary of the device state.
331      */
332     private static class SummaryAdapter extends BaseAdapter {
333         private static final int BASE_ID_HEADING = 1 << 10;
334         private static final int BASE_ID_DEVICE_ITEM = 2 << 10;
335         private static final int BASE_ID_AXIS_ITEM = 3 << 10;
336         private static final int BASE_ID_KEY_ITEM = 4 << 10;
337 
338         private final Context mContext;
339         private final Resources mResources;
340 
341         private final SparseArray<Item> mDataItems = new SparseArray<Item>();
342         private final ArrayList<Item> mVisibleItems = new ArrayList<Item>();
343 
344         private final Heading mDeviceHeading;
345         private final TextColumn mDeviceNameTextColumn;
346 
347         private final Heading mAxesHeading;
348         private final Heading mKeysHeading;
349 
350         private InputDeviceState mState;
351 
SummaryAdapter(Context context, Resources resources)352         public SummaryAdapter(Context context, Resources resources) {
353             mContext = context;
354             mResources = resources;
355 
356             mDeviceHeading = new Heading(BASE_ID_HEADING | 0,
357                     mResources.getString(R.string.game_controller_input_heading_device));
358             mDeviceNameTextColumn = new TextColumn(BASE_ID_DEVICE_ITEM | 0,
359                     mResources.getString(R.string.game_controller_input_label_device_name));
360 
361             mAxesHeading = new Heading(BASE_ID_HEADING | 1,
362                     mResources.getString(R.string.game_controller_input_heading_axes));
363             mKeysHeading = new Heading(BASE_ID_HEADING | 2,
364                     mResources.getString(R.string.game_controller_input_heading_keys));
365         }
366 
onItemClick(int position)367         public void onItemClick(int position) {
368             if (mState != null) {
369                 Toast toast = Toast.makeText(
370                         mContext, mState.getDevice().toString(), Toast.LENGTH_LONG);
371                 toast.show();
372             }
373         }
374 
show(InputDeviceState state)375         public void show(InputDeviceState state) {
376             mState = state;
377             mVisibleItems.clear();
378 
379             // Populate device information.
380             mVisibleItems.add(mDeviceHeading);
381             mDeviceNameTextColumn.setContent(state.getDevice().getName());
382             mVisibleItems.add(mDeviceNameTextColumn);
383 
384             // Populate axes.
385             mVisibleItems.add(mAxesHeading);
386             final int axisCount = state.getAxisCount();
387             for (int i = 0; i < axisCount; i++) {
388                 final int axis = state.getAxis(i);
389                 final int id = BASE_ID_AXIS_ITEM | axis;
390                 TextColumn column = (TextColumn) mDataItems.get(id);
391                 if (column == null) {
392                     column = new TextColumn(id, MotionEvent.axisToString(axis));
393                     mDataItems.put(id, column);
394                 }
395                 column.setContent(Float.toString(state.getAxisValue(i)));
396                 mVisibleItems.add(column);
397             }
398 
399             // Populate keys.
400             mVisibleItems.add(mKeysHeading);
401             final int keyCount = state.getKeyCount();
402             for (int i = 0; i < keyCount; i++) {
403                 final int keyCode = state.getKeyCode(i);
404                 final int id = BASE_ID_KEY_ITEM | keyCode;
405                 TextColumn column = (TextColumn) mDataItems.get(id);
406                 if (column == null) {
407                     column = new TextColumn(id, KeyEvent.keyCodeToString(keyCode));
408                     mDataItems.put(id, column);
409                 }
410                 column.setContent(mResources.getString(state.isKeyPressed(i)
411                         ? R.string.game_controller_input_key_pressed
412                         : R.string.game_controller_input_key_released));
413                 mVisibleItems.add(column);
414             }
415 
416             notifyDataSetChanged();
417         }
418 
419         @Override
hasStableIds()420         public boolean hasStableIds() {
421             return true;
422         }
423 
424         @Override
getCount()425         public int getCount() {
426             return mVisibleItems.size();
427         }
428 
429         @Override
getItem(int position)430         public Item getItem(int position) {
431             return mVisibleItems.get(position);
432         }
433 
434         @Override
getItemId(int position)435         public long getItemId(int position) {
436             return getItem(position).getItemId();
437         }
438 
439         @Override
getView(int position, View convertView, ViewGroup parent)440         public View getView(int position, View convertView, ViewGroup parent) {
441             return getItem(position).getView(convertView, parent);
442         }
443 
444         private static abstract class Item {
445             private final int mItemId;
446             private final int mLayoutResourceId;
447             private View mView;
448 
Item(int itemId, int layoutResourceId)449             public Item(int itemId, int layoutResourceId) {
450                 mItemId = itemId;
451                 mLayoutResourceId = layoutResourceId;
452             }
453 
getItemId()454             public long getItemId() {
455                 return mItemId;
456             }
457 
getView(View convertView, ViewGroup parent)458             public View getView(View convertView, ViewGroup parent) {
459                 if (mView == null) {
460                     LayoutInflater inflater = (LayoutInflater)
461                             parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
462                     mView = inflater.inflate(mLayoutResourceId, parent, false);
463                     initView(mView);
464                 }
465                 updateView(mView);
466                 return mView;
467             }
468 
initView(View view)469             protected void initView(View view) {
470             }
471 
updateView(View view)472             protected void updateView(View view) {
473             }
474         }
475 
476         private static class Heading extends Item {
477             private final String mLabel;
478 
Heading(int itemId, String label)479             public Heading(int itemId, String label) {
480                 super(itemId, R.layout.game_controller_input_heading);
481                 mLabel = label;
482             }
483 
484             @Override
initView(View view)485             public void initView(View view) {
486                 TextView textView = (TextView) view;
487                 textView.setText(mLabel);
488             }
489         }
490 
491         private static class TextColumn extends Item {
492             private final String mLabel;
493 
494             private String mContent;
495             private TextView mContentView;
496 
TextColumn(int itemId, String label)497             public TextColumn(int itemId, String label) {
498                 super(itemId, R.layout.game_controller_input_text_column);
499                 mLabel = label;
500             }
501 
setContent(String content)502             public void setContent(String content) {
503                 mContent = content;
504             }
505 
506             @Override
initView(View view)507             public void initView(View view) {
508                 TextView textView = (TextView) view.findViewById(R.id.label);
509                 textView.setText(mLabel);
510 
511                 mContentView = (TextView) view.findViewById(R.id.content);
512             }
513 
514             @Override
updateView(View view)515             public void updateView(View view) {
516                 mContentView.setText(mContent);
517             }
518         }
519     }
520 }
521