Home | History | Annotate | Download | only in view
      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
     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
     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
    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
    114     public void onWindowFocusChanged(boolean hasFocus) {
    115         super.onWindowFocusChanged(hasFocus);
    116 
    117         mGame.requestFocus();
    118     }
    119 
    120     @Override
    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
    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 
    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
    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
    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
    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 
    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 
    231         public InputDevice getDevice() {
    232             return mDevice;
    233         }
    234 
    235         public int getAxisCount() {
    236             return mAxes.length;
    237         }
    238 
    239         public int getAxis(int axisIndex) {
    240             return mAxes[axisIndex];
    241         }
    242 
    243         public float getAxisValue(int axisIndex) {
    244             return mAxisValues[axisIndex];
    245         }
    246 
    247         public int getKeyCount() {
    248             return mKeys.size();
    249         }
    250 
    251         public int getKeyCode(int keyIndex) {
    252             return mKeys.keyAt(keyIndex);
    253         }
    254 
    255         public boolean isKeyPressed(int keyIndex) {
    256             return mKeys.valueAt(keyIndex) != 0;
    257         }
    258 
    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 
    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 
    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.
    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 
    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 
    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 
    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
    420         public boolean hasStableIds() {
    421             return true;
    422         }
    423 
    424         @Override
    425         public int getCount() {
    426             return mVisibleItems.size();
    427         }
    428 
    429         @Override
    430         public Item getItem(int position) {
    431             return mVisibleItems.get(position);
    432         }
    433 
    434         @Override
    435         public long getItemId(int position) {
    436             return getItem(position).getItemId();
    437         }
    438 
    439         @Override
    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 
    449             public Item(int itemId, int layoutResourceId) {
    450                 mItemId = itemId;
    451                 mLayoutResourceId = layoutResourceId;
    452             }
    453 
    454             public long getItemId() {
    455                 return mItemId;
    456             }
    457 
    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 
    469             protected void initView(View view) {
    470             }
    471 
    472             protected void updateView(View view) {
    473             }
    474         }
    475 
    476         private static class Heading extends Item {
    477             private final String mLabel;
    478 
    479             public Heading(int itemId, String label) {
    480                 super(itemId, R.layout.game_controller_input_heading);
    481                 mLabel = label;
    482             }
    483 
    484             @Override
    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 
    497             public TextColumn(int itemId, String label) {
    498                 super(itemId, R.layout.game_controller_input_text_column);
    499                 mLabel = label;
    500             }
    501 
    502             public void setContent(String content) {
    503                 mContent = content;
    504             }
    505 
    506             @Override
    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
    515             public void updateView(View view) {
    516                 mContentView.setText(mContent);
    517             }
    518         }
    519     }
    520 }
    521