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