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