blob: 8aea9491626488517211c881c5308988b83c52fd [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.apis.view;
import com.example.android.apis.R;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.InputDevice.MotionRange;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicLong;
/**
* Demonstrates how to process input events received from game controllers.
*
* This activity displays button states and joystick positions.
* Also writes detailed information about relevant input events to the log.
*
* The game controller is also uses to control a very simple game. See {@link GameView}
* for the game itself.
*/
public class GameControllerInput extends Activity {
private static final String TAG = "GameControllerInput";
private SparseArray<InputDeviceState> mInputDeviceStates;
private GameView mGame;
private ListView mSummaryList;
private SummaryAdapter mSummaryAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mInputDeviceStates = new SparseArray<InputDeviceState>();
mSummaryAdapter = new SummaryAdapter(this, getResources());
setContentView(R.layout.game_controller_input);
mGame = (GameView) findViewById(R.id.game);
mSummaryList = (ListView) findViewById(R.id.summary);
mSummaryList.setAdapter(mSummaryAdapter);
mSummaryList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mSummaryAdapter.onItemClick(position);
}
});
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
mGame.requestFocus();
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
// Update device state for visualization and logging.
InputDeviceState state = getInputDeviceState(event);
if (state != null) {
switch (event.getAction()) {
case KeyEvent.ACTION_DOWN:
if (state.onKeyDown(event)) {
mSummaryAdapter.show(state);
}
break;
case KeyEvent.ACTION_UP:
if (state.onKeyUp(event)) {
mSummaryAdapter.show(state);
}
break;
}
}
return super.dispatchKeyEvent(event);
}
@Override
public boolean dispatchGenericMotionEvent(MotionEvent event) {
// Check that the event came from a joystick since a generic motion event
// could be almost anything.
if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0
&& event.getAction() == MotionEvent.ACTION_MOVE) {
// Update device state for visualization and logging.
InputDeviceState state = getInputDeviceState(event);
if (state != null && state.onJoystickMotion(event)) {
mSummaryAdapter.show(state);
}
}
return super.dispatchGenericMotionEvent(event);
}
private InputDeviceState getInputDeviceState(InputEvent event) {
final int deviceId = event.getDeviceId();
InputDeviceState state = mInputDeviceStates.get(deviceId);
if (state == null) {
final InputDevice device = event.getDevice();
if (device == null) {
return null;
}
state = new InputDeviceState(device);
mInputDeviceStates.put(deviceId, state);
Log.i(TAG, device.toString());
}
return state;
}
/**
* Tracks the state of joystick axes and game controller buttons for a particular
* input device for diagnostic purposes.
*/
private static class InputDeviceState {
private final InputDevice mDevice;
private final int[] mAxes;
private final float[] mAxisValues;
private final SparseIntArray mKeys;
public InputDeviceState(InputDevice device) {
mDevice = device;
int numAxes = 0;
for (MotionRange range : device.getMotionRanges()) {
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
numAxes += 1;
}
}
mAxes = new int[numAxes];
mAxisValues = new float[numAxes];
int i = 0;
for (MotionRange range : device.getMotionRanges()) {
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
numAxes += 1;
}
mAxes[i++] = range.getAxis();
}
mKeys = new SparseIntArray();
}
public InputDevice getDevice() {
return mDevice;
}
public int getAxisCount() {
return mAxes.length;
}
public int getAxis(int axisIndex) {
return mAxes[axisIndex];
}
public float getAxisValue(int axisIndex) {
return mAxisValues[axisIndex];
}
public int getKeyCount() {
return mKeys.size();
}
public int getKeyCode(int keyIndex) {
return mKeys.keyAt(keyIndex);
}
public boolean isKeyPressed(int keyIndex) {
return mKeys.valueAt(keyIndex) != 0;
}
public boolean onKeyDown(KeyEvent event) {
final int keyCode = event.getKeyCode();
if (isGameKey(keyCode)) {
if (event.getRepeatCount() == 0) {
final String symbolicName = KeyEvent.keyCodeToString(keyCode);
mKeys.put(keyCode, 1);
Log.i(TAG, mDevice.getName() + " - Key Down: " + symbolicName);
}
return true;
}
return false;
}
public boolean onKeyUp(KeyEvent event) {
final int keyCode = event.getKeyCode();
if (isGameKey(keyCode)) {
int index = mKeys.indexOfKey(keyCode);
if (index >= 0) {
final String symbolicName = KeyEvent.keyCodeToString(keyCode);
mKeys.put(keyCode, 0);
Log.i(TAG, mDevice.getName() + " - Key Up: " + symbolicName);
}
return true;
}
return false;
}
public boolean onJoystickMotion(MotionEvent event) {
StringBuilder message = new StringBuilder();
message.append(mDevice.getName()).append(" - Joystick Motion:\n");
final int historySize = event.getHistorySize();
for (int i = 0; i < mAxes.length; i++) {
final int axis = mAxes[i];
final float value = event.getAxisValue(axis);
mAxisValues[i] = value;
message.append(" ").append(MotionEvent.axisToString(axis)).append(": ");
// Append all historical values in the batch.
for (int historyPos = 0; historyPos < historySize; historyPos++) {
message.append(event.getHistoricalAxisValue(axis, historyPos));
message.append(", ");
}
// Append the current value.
message.append(value);
message.append("\n");
}
Log.i(TAG, message.toString());
return true;
}
// Check whether this is a key we care about.
// In a real game, we would probably let the user configure which keys to use
// instead of hardcoding the keys like this.
private static boolean isGameKey(int keyCode) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN:
case KeyEvent.KEYCODE_DPAD_LEFT:
case KeyEvent.KEYCODE_DPAD_RIGHT:
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_SPACE:
return true;
default:
return KeyEvent.isGamepadButton(keyCode);
}
}
}
/**
* A list adapter that displays a summary of the device state.
*/
private static class SummaryAdapter extends BaseAdapter {
private static final int BASE_ID_HEADING = 1 << 10;
private static final int BASE_ID_DEVICE_ITEM = 2 << 10;
private static final int BASE_ID_AXIS_ITEM = 3 << 10;
private static final int BASE_ID_KEY_ITEM = 4 << 10;
private final Context mContext;
private final Resources mResources;
private final SparseArray<Item> mDataItems = new SparseArray<Item>();
private final ArrayList<Item> mVisibleItems = new ArrayList<Item>();
private final Heading mDeviceHeading;
private final TextColumn mDeviceNameTextColumn;
private final Heading mAxesHeading;
private final Heading mKeysHeading;
private InputDeviceState mState;
public SummaryAdapter(Context context, Resources resources) {
mContext = context;
mResources = resources;
mDeviceHeading = new Heading(BASE_ID_HEADING | 0,
mResources.getString(R.string.game_controller_input_heading_device));
mDeviceNameTextColumn = new TextColumn(BASE_ID_DEVICE_ITEM | 0,
mResources.getString(R.string.game_controller_input_label_device_name));
mAxesHeading = new Heading(BASE_ID_HEADING | 1,
mResources.getString(R.string.game_controller_input_heading_axes));
mKeysHeading = new Heading(BASE_ID_HEADING | 2,
mResources.getString(R.string.game_controller_input_heading_keys));
}
public void onItemClick(int position) {
if (mState != null) {
Toast toast = Toast.makeText(
mContext, mState.getDevice().toString(), Toast.LENGTH_LONG);
toast.show();
}
}
public void show(InputDeviceState state) {
mState = state;
mVisibleItems.clear();
// Populate device information.
mVisibleItems.add(mDeviceHeading);
mDeviceNameTextColumn.setContent(state.getDevice().getName());
mVisibleItems.add(mDeviceNameTextColumn);
// Populate axes.
mVisibleItems.add(mAxesHeading);
final int axisCount = state.getAxisCount();
for (int i = 0; i < axisCount; i++) {
final int axis = state.getAxis(i);
final int id = BASE_ID_AXIS_ITEM | axis;
TextColumn column = (TextColumn) mDataItems.get(id);
if (column == null) {
column = new TextColumn(id, MotionEvent.axisToString(axis));
mDataItems.put(id, column);
}
column.setContent(Float.toString(state.getAxisValue(i)));
mVisibleItems.add(column);
}
// Populate keys.
mVisibleItems.add(mKeysHeading);
final int keyCount = state.getKeyCount();
for (int i = 0; i < keyCount; i++) {
final int keyCode = state.getKeyCode(i);
final int id = BASE_ID_KEY_ITEM | keyCode;
TextColumn column = (TextColumn) mDataItems.get(id);
if (column == null) {
column = new TextColumn(id, KeyEvent.keyCodeToString(keyCode));
mDataItems.put(id, column);
}
column.setContent(mResources.getString(state.isKeyPressed(i)
? R.string.game_controller_input_key_pressed
: R.string.game_controller_input_key_released));
mVisibleItems.add(column);
}
notifyDataSetChanged();
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public int getCount() {
return mVisibleItems.size();
}
@Override
public Item getItem(int position) {
return mVisibleItems.get(position);
}
@Override
public long getItemId(int position) {
return getItem(position).getItemId();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return getItem(position).getView(convertView, parent);
}
private static abstract class Item {
private final int mItemId;
private final int mLayoutResourceId;
private View mView;
public Item(int itemId, int layoutResourceId) {
mItemId = itemId;
mLayoutResourceId = layoutResourceId;
}
public long getItemId() {
return mItemId;
}
public View getView(View convertView, ViewGroup parent) {
if (mView == null) {
LayoutInflater inflater = (LayoutInflater)
parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mView = inflater.inflate(mLayoutResourceId, parent, false);
initView(mView);
}
updateView(mView);
return mView;
}
protected void initView(View view) {
}
protected void updateView(View view) {
}
}
private static class Heading extends Item {
private final String mLabel;
public Heading(int itemId, String label) {
super(itemId, R.layout.game_controller_input_heading);
mLabel = label;
}
@Override
public void initView(View view) {
TextView textView = (TextView) view;
textView.setText(mLabel);
}
}
private static class TextColumn extends Item {
private final String mLabel;
private String mContent;
private TextView mContentView;
public TextColumn(int itemId, String label) {
super(itemId, R.layout.game_controller_input_text_column);
mLabel = label;
}
public void setContent(String content) {
mContent = content;
}
@Override
public void initView(View view) {
TextView textView = (TextView) view.findViewById(R.id.label);
textView.setText(mLabel);
mContentView = (TextView) view.findViewById(R.id.content);
}
@Override
public void updateView(View view) {
mContentView.setText(mContent);
}
}
}
}