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