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 (isJoystick(event.getSource())
    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     private static boolean isJoystick(int source) {
    199         return (source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0;
    200     }
    201 
    202     /**
    203      * Tracks the state of joystick axes and game controller buttons for a particular
    204      * input device for diagnostic purposes.
    205      */
    206     private static class InputDeviceState {
    207         private final InputDevice mDevice;
    208         private final int[] mAxes;
    209         private final float[] mAxisValues;
    210         private final SparseIntArray mKeys;
    211 
    212         public InputDeviceState(InputDevice device) {
    213             mDevice = device;
    214 
    215             int numAxes = 0;
    216             final List<MotionRange> ranges = device.getMotionRanges();
    217             for (MotionRange range : ranges) {
    218                 if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
    219                     numAxes += 1;
    220                 }
    221             }
    222 
    223             mAxes = new int[numAxes];
    224             mAxisValues = new float[numAxes];
    225             int i = 0;
    226             for (MotionRange range : ranges) {
    227                 if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
    228                     mAxes[i++] = range.getAxis();
    229                 }
    230             }
    231 
    232             mKeys = new SparseIntArray();
    233         }
    234 
    235         public InputDevice getDevice() {
    236             return mDevice;
    237         }
    238 
    239         public int getAxisCount() {
    240             return mAxes.length;
    241         }
    242 
    243         public int getAxis(int axisIndex) {
    244             return mAxes[axisIndex];
    245         }
    246 
    247         public float getAxisValue(int axisIndex) {
    248             return mAxisValues[axisIndex];
    249         }
    250 
    251         public int getKeyCount() {
    252             return mKeys.size();
    253         }
    254 
    255         public int getKeyCode(int keyIndex) {
    256             return mKeys.keyAt(keyIndex);
    257         }
    258 
    259         public boolean isKeyPressed(int keyIndex) {
    260             return mKeys.valueAt(keyIndex) != 0;
    261         }
    262 
    263         public boolean onKeyDown(KeyEvent event) {
    264             final int keyCode = event.getKeyCode();
    265             if (isGameKey(keyCode)) {
    266                 if (event.getRepeatCount() == 0) {
    267                     final String symbolicName = KeyEvent.keyCodeToString(keyCode);
    268                     mKeys.put(keyCode, 1);
    269                     Log.i(TAG, mDevice.getName() + " - Key Down: " + symbolicName);
    270                 }
    271                 return true;
    272             }
    273             return false;
    274         }
    275 
    276         public boolean onKeyUp(KeyEvent event) {
    277             final int keyCode = event.getKeyCode();
    278             if (isGameKey(keyCode)) {
    279                 int index = mKeys.indexOfKey(keyCode);
    280                 if (index >= 0) {
    281                     final String symbolicName = KeyEvent.keyCodeToString(keyCode);
    282                     mKeys.put(keyCode, 0);
    283                     Log.i(TAG, mDevice.getName() + " - Key Up: " + symbolicName);
    284                 }
    285                 return true;
    286             }
    287             return false;
    288         }
    289 
    290         public boolean onJoystickMotion(MotionEvent event) {
    291             StringBuilder message = new StringBuilder();
    292             message.append(mDevice.getName()).append(" - Joystick Motion:\n");
    293 
    294             final int historySize = event.getHistorySize();
    295             for (int i = 0; i < mAxes.length; i++) {
    296                 final int axis = mAxes[i];
    297                 final float value = event.getAxisValue(axis);
    298                 mAxisValues[i] = value;
    299                 message.append("  ").append(MotionEvent.axisToString(axis)).append(": ");
    300 
    301                 // Append all historical values in the batch.
    302                 for (int historyPos = 0; historyPos < historySize; historyPos++) {
    303                     message.append(event.getHistoricalAxisValue(axis, historyPos));
    304                     message.append(", ");
    305                 }
    306 
    307                 // Append the current value.
    308                 message.append(value);
    309                 message.append("\n");
    310             }
    311             Log.i(TAG, message.toString());
    312             return true;
    313         }
    314 
    315         // Check whether this is a key we care about.
    316         // In a real game, we would probably let the user configure which keys to use
    317         // instead of hardcoding the keys like this.
    318         private static boolean isGameKey(int keyCode) {
    319             switch (keyCode) {
    320                 case KeyEvent.KEYCODE_DPAD_UP:
    321                 case KeyEvent.KEYCODE_DPAD_DOWN:
    322                 case KeyEvent.KEYCODE_DPAD_LEFT:
    323                 case KeyEvent.KEYCODE_DPAD_RIGHT:
    324                 case KeyEvent.KEYCODE_DPAD_CENTER:
    325                 case KeyEvent.KEYCODE_SPACE:
    326                     return true;
    327                 default:
    328                     return KeyEvent.isGamepadButton(keyCode);
    329             }
    330         }
    331     }
    332 
    333     /**
    334      * A list adapter that displays a summary of the device state.
    335      */
    336     private static class SummaryAdapter extends BaseAdapter {
    337         private static final int BASE_ID_HEADING = 1 << 10;
    338         private static final int BASE_ID_DEVICE_ITEM = 2 << 10;
    339         private static final int BASE_ID_AXIS_ITEM = 3 << 10;
    340         private static final int BASE_ID_KEY_ITEM = 4 << 10;
    341 
    342         private final Context mContext;
    343         private final Resources mResources;
    344 
    345         private final SparseArray<Item> mDataItems = new SparseArray<Item>();
    346         private final ArrayList<Item> mVisibleItems = new ArrayList<Item>();
    347 
    348         private final Heading mDeviceHeading;
    349         private final TextColumn mDeviceNameTextColumn;
    350 
    351         private final Heading mAxesHeading;
    352         private final Heading mKeysHeading;
    353 
    354         private InputDeviceState mState;
    355 
    356         public SummaryAdapter(Context context, Resources resources) {
    357             mContext = context;
    358             mResources = resources;
    359 
    360             mDeviceHeading = new Heading(BASE_ID_HEADING | 0,
    361                     mResources.getString(R.string.game_controller_input_heading_device));
    362             mDeviceNameTextColumn = new TextColumn(BASE_ID_DEVICE_ITEM | 0,
    363                     mResources.getString(R.string.game_controller_input_label_device_name));
    364 
    365             mAxesHeading = new Heading(BASE_ID_HEADING | 1,
    366                     mResources.getString(R.string.game_controller_input_heading_axes));
    367             mKeysHeading = new Heading(BASE_ID_HEADING | 2,
    368                     mResources.getString(R.string.game_controller_input_heading_keys));
    369         }
    370 
    371         public void onItemClick(int position) {
    372             if (mState != null) {
    373                 Toast toast = Toast.makeText(
    374                         mContext, mState.getDevice().toString(), Toast.LENGTH_LONG);
    375                 toast.show();
    376             }
    377         }
    378 
    379         public void show(InputDeviceState state) {
    380             mState = state;
    381             mVisibleItems.clear();
    382 
    383             // Populate device information.
    384             mVisibleItems.add(mDeviceHeading);
    385             mDeviceNameTextColumn.setContent(state.getDevice().getName());
    386             mVisibleItems.add(mDeviceNameTextColumn);
    387 
    388             // Populate axes.
    389             mVisibleItems.add(mAxesHeading);
    390             final int axisCount = state.getAxisCount();
    391             for (int i = 0; i < axisCount; i++) {
    392                 final int axis = state.getAxis(i);
    393                 final int id = BASE_ID_AXIS_ITEM | axis;
    394                 TextColumn column = (TextColumn) mDataItems.get(id);
    395                 if (column == null) {
    396                     column = new TextColumn(id, MotionEvent.axisToString(axis));
    397                     mDataItems.put(id, column);
    398                 }
    399                 column.setContent(Float.toString(state.getAxisValue(i)));
    400                 mVisibleItems.add(column);
    401             }
    402 
    403             // Populate keys.
    404             mVisibleItems.add(mKeysHeading);
    405             final int keyCount = state.getKeyCount();
    406             for (int i = 0; i < keyCount; i++) {
    407                 final int keyCode = state.getKeyCode(i);
    408                 final int id = BASE_ID_KEY_ITEM | keyCode;
    409                 TextColumn column = (TextColumn) mDataItems.get(id);
    410                 if (column == null) {
    411                     column = new TextColumn(id, KeyEvent.keyCodeToString(keyCode));
    412                     mDataItems.put(id, column);
    413                 }
    414                 column.setContent(mResources.getString(state.isKeyPressed(i)
    415                         ? R.string.game_controller_input_key_pressed
    416                         : R.string.game_controller_input_key_released));
    417                 mVisibleItems.add(column);
    418             }
    419 
    420             notifyDataSetChanged();
    421         }
    422 
    423         @Override
    424         public boolean hasStableIds() {
    425             return true;
    426         }
    427 
    428         @Override
    429         public int getCount() {
    430             return mVisibleItems.size();
    431         }
    432 
    433         @Override
    434         public Item getItem(int position) {
    435             return mVisibleItems.get(position);
    436         }
    437 
    438         @Override
    439         public long getItemId(int position) {
    440             return getItem(position).getItemId();
    441         }
    442 
    443         @Override
    444         public View getView(int position, View convertView, ViewGroup parent) {
    445             return getItem(position).getView(convertView, parent);
    446         }
    447 
    448         private static abstract class Item {
    449             private final int mItemId;
    450             private final int mLayoutResourceId;
    451             private View mView;
    452 
    453             public Item(int itemId, int layoutResourceId) {
    454                 mItemId = itemId;
    455                 mLayoutResourceId = layoutResourceId;
    456             }
    457 
    458             public long getItemId() {
    459                 return mItemId;
    460             }
    461 
    462             public View getView(View convertView, ViewGroup parent) {
    463                 if (mView == null) {
    464                     LayoutInflater inflater = (LayoutInflater)
    465                             parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    466                     mView = inflater.inflate(mLayoutResourceId, parent, false);
    467                     initView(mView);
    468                 }
    469                 updateView(mView);
    470                 return mView;
    471             }
    472 
    473             protected void initView(View view) {
    474             }
    475 
    476             protected void updateView(View view) {
    477             }
    478         }
    479 
    480         private static class Heading extends Item {
    481             private final String mLabel;
    482 
    483             public Heading(int itemId, String label) {
    484                 super(itemId, R.layout.game_controller_input_heading);
    485                 mLabel = label;
    486             }
    487 
    488             @Override
    489             public void initView(View view) {
    490                 TextView textView = (TextView) view;
    491                 textView.setText(mLabel);
    492             }
    493         }
    494 
    495         private static class TextColumn extends Item {
    496             private final String mLabel;
    497 
    498             private String mContent;
    499             private TextView mContentView;
    500 
    501             public TextColumn(int itemId, String label) {
    502                 super(itemId, R.layout.game_controller_input_text_column);
    503                 mLabel = label;
    504             }
    505 
    506             public void setContent(String content) {
    507                 mContent = content;
    508             }
    509 
    510             @Override
    511             public void initView(View view) {
    512                 TextView textView = (TextView) view.findViewById(R.id.label);
    513                 textView.setText(mLabel);
    514 
    515                 mContentView = (TextView) view.findViewById(R.id.content);
    516             }
    517 
    518             @Override
    519             public void updateView(View view) {
    520                 mContentView.setText(mContent);
    521             }
    522         }
    523     }
    524 }
    525