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.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
     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
     87     public void onWindowFocusChanged(boolean hasFocus) {
     88         super.onWindowFocusChanged(hasFocus);
     89 
     90         mGame.requestFocus();
     91     }
     92 
     93     @Override
     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
    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 
    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 
    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 
    178         public InputDevice getDevice() {
    179             return mDevice;
    180         }
    181 
    182         public int getAxisCount() {
    183             return mAxes.length;
    184         }
    185 
    186         public int getAxis(int axisIndex) {
    187             return mAxes[axisIndex];
    188         }
    189 
    190         public float getAxisValue(int axisIndex) {
    191             return mAxisValues[axisIndex];
    192         }
    193 
    194         public int getKeyCount() {
    195             return mKeys.size();
    196         }
    197 
    198         public int getKeyCode(int keyIndex) {
    199             return mKeys.keyAt(keyIndex);
    200         }
    201 
    202         public boolean isKeyPressed(int keyIndex) {
    203             return mKeys.valueAt(keyIndex) != 0;
    204         }
    205 
    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 
    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 
    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.
    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 
    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 
    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 
    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
    367         public boolean hasStableIds() {
    368             return true;
    369         }
    370 
    371         @Override
    372         public int getCount() {
    373             return mVisibleItems.size();
    374         }
    375 
    376         @Override
    377         public Item getItem(int position) {
    378             return mVisibleItems.get(position);
    379         }
    380 
    381         @Override
    382         public long getItemId(int position) {
    383             return getItem(position).getItemId();
    384         }
    385 
    386         @Override
    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 
    396             public Item(int itemId, int layoutResourceId) {
    397                 mItemId = itemId;
    398                 mLayoutResourceId = layoutResourceId;
    399             }
    400 
    401             public long getItemId() {
    402                 return mItemId;
    403             }
    404 
    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 
    416             protected void initView(View view) {
    417             }
    418 
    419             protected void updateView(View view) {
    420             }
    421         }
    422 
    423         private static class Heading extends Item {
    424             private final String mLabel;
    425 
    426             public Heading(int itemId, String label) {
    427                 super(itemId, R.layout.game_controller_input_heading);
    428                 mLabel = label;
    429             }
    430 
    431             @Override
    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 
    444             public TextColumn(int itemId, String label) {
    445                 super(itemId, R.layout.game_controller_input_text_column);
    446                 mLabel = label;
    447             }
    448 
    449             public void setContent(String content) {
    450                 mContent = content;
    451             }
    452 
    453             @Override
    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
    462             public void updateView(View view) {
    463                 mContentView.setText(mContent);
    464             }
    465         }
    466     }
    467 }
    468