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