/* * 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.hardware.input.InputManager; import android.os.Bundle; import android.util.Log; import android.util.SparseArray; import android.util.SparseIntArray; import android.view.InputDevice; 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.List; /** * Demonstrates how to process input events received from game controllers. * It also shows how to detect when input devices are added, removed or reconfigured. * * 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 implements InputManager.InputDeviceListener { private static final String TAG = "GameControllerInput"; private InputManager mInputManager; private SparseArray<InputDeviceState> mInputDeviceStates; private GameView mGame; private ListView mSummaryList; private SummaryAdapter mSummaryAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mInputManager = (InputManager)getSystemService(Context.INPUT_SERVICE); 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 protected void onResume() { super.onResume(); // Register an input device listener to watch when input devices are // added, removed or reconfigured. mInputManager.registerInputDeviceListener(this, null); // Query all input devices. // We do this so that we can see them in the log as they are enumerated. int[] ids = mInputManager.getInputDeviceIds(); for (int i = 0; i < ids.length; i++) { getInputDeviceState(ids[i]); } } @Override protected void onPause() { super.onPause(); // Remove the input device listener when the activity is paused. mInputManager.unregisterInputDeviceListener(this); } @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.getDeviceId()); 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.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK) && event.getAction() == MotionEvent.ACTION_MOVE) { // Update device state for visualization and logging. InputDeviceState state = getInputDeviceState(event.getDeviceId()); if (state != null && state.onJoystickMotion(event)) { mSummaryAdapter.show(state); } } return super.dispatchGenericMotionEvent(event); } private InputDeviceState getInputDeviceState(int deviceId) { InputDeviceState state = mInputDeviceStates.get(deviceId); if (state == null) { final InputDevice device = mInputManager.getInputDevice(deviceId); if (device == null) { return null; } state = new InputDeviceState(device); mInputDeviceStates.put(deviceId, state); Log.i(TAG, "Device enumerated: " + state.mDevice); } return state; } // Implementation of InputManager.InputDeviceListener.onInputDeviceAdded() @Override public void onInputDeviceAdded(int deviceId) { InputDeviceState state = getInputDeviceState(deviceId); Log.i(TAG, "Device added: " + state.mDevice); } // Implementation of InputManager.InputDeviceListener.onInputDeviceChanged() @Override public void onInputDeviceChanged(int deviceId) { InputDeviceState state = mInputDeviceStates.get(deviceId); if (state != null) { mInputDeviceStates.remove(deviceId); state = getInputDeviceState(deviceId); Log.i(TAG, "Device changed: " + state.mDevice); } } // Implementation of InputManager.InputDeviceListener.onInputDeviceRemoved() @Override public void onInputDeviceRemoved(int deviceId) { InputDeviceState state = mInputDeviceStates.get(deviceId); if (state != null) { Log.i(TAG, "Device removed: " + state.mDevice); mInputDeviceStates.remove(deviceId); } } /** * 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; final List<MotionRange> ranges = device.getMotionRanges(); for (MotionRange range : ranges) { if (range.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK)) { numAxes += 1; } } mAxes = new int[numAxes]; mAxisValues = new float[numAxes]; int i = 0; for (MotionRange range : ranges) { if (range.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK)) { 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); } } } }