Hardware Android

/*
 * 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 mInputDeviceStates;
    private GameView mGame;
    private ListView mSummaryList;
    private SummaryAdapter mSummaryAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mInputDeviceStates = new SparseArray();
        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 mDataItems = new SparseArray();
        private final ArrayList mVisibleItems = new ArrayList();
        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);
            }
        }
    }
}
/*
 * 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 android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Paint.Style;
import android.os.Handler;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
 * A trivial joystick based physics game to demonstrate joystick handling.
 *
 * @see GameControllerInput
 */
public class GameView extends View {
    private final long ANIMATION_TIME_STEP = 1000 / 60;
    private final int MAX_OBSTACLES = 12;
    private final Random mRandom;
    private Ship mShip;
    private final List mBullets;
    private final List mObstacles;
    private long mLastStepTime;
    private InputDevice mLastInputDevice;
    private static final int DPAD_STATE_LEFT  = 1 << 0;
    private static final int DPAD_STATE_RIGHT = 1 << 1;
    private static final int DPAD_STATE_UP    = 1 << 2;
    private static final int DPAD_STATE_DOWN  = 1 << 3;
    private int mDPadState;
    private float mShipSize;
    private float mMaxShipThrust;
    private float mMaxShipSpeed;
    private float mBulletSize;
    private float mBulletSpeed;
    private float mMinObstacleSize;
    private float mMaxObstacleSize;
    private float mMinObstacleSpeed;
    private float mMaxObstacleSpeed;
    private final Runnable mAnimationRunnable = new Runnable() {
        public void run() {
            animateFrame();
        }
    };
    public GameView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mRandom = new Random();
        mBullets = new ArrayList();
        mObstacles = new ArrayList();
        setFocusable(true);
        setFocusableInTouchMode(true);
        float baseSize = getContext().getResources().getDisplayMetrics().density * 5f;
        float baseSpeed = baseSize * 3;
        mShipSize = baseSize * 3;
        mMaxShipThrust = baseSpeed * 0.25f;
        mMaxShipSpeed = baseSpeed * 12;
        mBulletSize = baseSize;
        mBulletSpeed = baseSpeed * 12;
        mMinObstacleSize = baseSize * 2;
        mMaxObstacleSize = baseSize * 12;
        mMinObstacleSpeed = baseSpeed;
        mMaxObstacleSpeed = baseSpeed * 3;
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // Reset the game when the view changes size.
        reset();
    }
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        ensureInitialized();
        // Handle DPad keys and fire button on initial down but not on auto-repeat.
        boolean handled = false;
        if (event.getRepeatCount() == 0) {
            switch (keyCode) {
                case KeyEvent.KEYCODE_DPAD_LEFT:
                    mShip.setHeadingX(-1);
                    mDPadState |= DPAD_STATE_LEFT;
                    handled = true;
                    break;
                case KeyEvent.KEYCODE_DPAD_RIGHT:
                    mShip.setHeadingX(1);
                    mDPadState |= DPAD_STATE_RIGHT;
                    handled = true;
                    break;
                case KeyEvent.KEYCODE_DPAD_UP:
                    mShip.setHeadingY(-1);
                    mDPadState |= DPAD_STATE_UP;
                    handled = true;
                    break;
                case KeyEvent.KEYCODE_DPAD_DOWN:
                    mShip.setHeadingY(1);
                    mDPadState |= DPAD_STATE_DOWN;
                    handled = true;
                    break;
                default:
                    if (isFireKey(keyCode)) {
                        fire();
                        handled = true;
                    }
                    break;
            }
        }
        if (handled) {
            step(event.getEventTime());
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        ensureInitialized();
        // Handle keys going up.
        boolean handled = false;
        switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_LEFT:
                mShip.setHeadingX(0);
                mDPadState &= ~DPAD_STATE_LEFT;
                handled = true;
                break;
            case KeyEvent.KEYCODE_DPAD_RIGHT:
                mShip.setHeadingX(0);
                mDPadState &= ~DPAD_STATE_RIGHT;
                handled = true;
                break;
            case KeyEvent.KEYCODE_DPAD_UP:
                mShip.setHeadingY(0);
                mDPadState &= ~DPAD_STATE_UP;
                handled = true;
                break;
            case KeyEvent.KEYCODE_DPAD_DOWN:
                mShip.setHeadingY(0);
                mDPadState &= ~DPAD_STATE_DOWN;
                handled = true;
                break;
            default:
                if (isFireKey(keyCode)) {
                    handled = true;
                }
                break;
        }
        if (handled) {
            step(event.getEventTime());
            return true;
        }
        return super.onKeyUp(keyCode, event);
    }
    private static boolean isFireKey(int keyCode) {
        return KeyEvent.isGamepadButton(keyCode)
                || keyCode == KeyEvent.KEYCODE_DPAD_CENTER
                || keyCode == KeyEvent.KEYCODE_SPACE;
    }
    @Override
    public boolean onGenericMotionEvent(MotionEvent event) {
        ensureInitialized();
        // 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) {
            // Cache the most recently obtained device information.
            // The device information may change over time but it can be
            // somewhat expensive to query.
            if (mLastInputDevice == null || mLastInputDevice.getId() != event.getDeviceId()) {
                mLastInputDevice = event.getDevice();
                // It's possible for the device id to be invalid.
                // In that case, getDevice() will return null.
                if (mLastInputDevice == null) {
                    return false;
                }
            }
            // Ignore joystick while the DPad is pressed to avoid conflicting motions.
            if (mDPadState != 0) {
                return true;
            }
            // Process all historical movement samples in the batch.
            final int historySize = event.getHistorySize();
            for (int i = 0; i < historySize; i++) {
                processJoystickInput(event, i);
            }
            // Process the current movement sample in the batch.
            processJoystickInput(event, -1);
            return true;
        }
        return super.onGenericMotionEvent(event);
    }
    private void processJoystickInput(MotionEvent event, int historyPos) {
        // Get joystick position.
        // Many game pads with two joysticks report the position of the second joystick
        // using the Z and RZ axes so we also handle those.
        // In a real game, we would allow the user to configure the axes manually.
        float x = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_X, historyPos);
        if (x == 0) {
            x = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_HAT_X, historyPos);
        }
        if (x == 0) {
            x = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_Z, historyPos);
        }
        float y = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_Y, historyPos);
        if (y == 0) {
            y = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_HAT_Y, historyPos);
        }
        if (y == 0) {
            y = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_RZ, historyPos);
        }
        // Set the ship heading.
        mShip.setHeading(x, y);
        step(historyPos < 0 ? event.getEventTime() : event.getHistoricalEventTime(historyPos));
    }
    private static float getCenteredAxis(MotionEvent event, InputDevice device,
            int axis, int historyPos) {
        final InputDevice.MotionRange range = device.getMotionRange(axis, event.getSource());
        if (range != null) {
            final float flat = range.getFlat();
            final float value = historyPos < 0 ? event.getAxisValue(axis)
                    : event.getHistoricalAxisValue(axis, historyPos);
            // Ignore axis values that are within the 'flat' region of the joystick axis center.
            // A joystick at rest does not always report an absolute position of (0,0).
            if (Math.abs(value) > flat) {
                return value;
            }
        }
        return 0;
    }
    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        // Turn on and off animations based on the window focus.
        // Alternately, we could update the game state using the Activity onResume()
        // and onPause() lifecycle events.
        if (hasWindowFocus) {
            getHandler().postDelayed(mAnimationRunnable, ANIMATION_TIME_STEP);
            mLastStepTime = SystemClock.uptimeMillis();
        } else {
            getHandler().removeCallbacks(mAnimationRunnable);
            mDPadState = 0;
            if (mShip != null) {
                mShip.setHeading(0, 0);
                mShip.setVelocity(0, 0);
            }
        }
        super.onWindowFocusChanged(hasWindowFocus);
    }
    private void fire() {
        if (mShip != null && !mShip.isDestroyed()) {
            Bullet bullet = new Bullet();
            bullet.setPosition(mShip.getBulletInitialX(), mShip.getBulletInitialY());
            bullet.setVelocity(mShip.getBulletVelocityX(mBulletSpeed),
                    mShip.getBulletVelocityY(mBulletSpeed));
            mBullets.add(bullet);
        }
    }
    private void ensureInitialized() {
        if (mShip == null) {
            reset();
        }
    }
    private void reset() {
        mShip = new Ship();
        mBullets.clear();
        mObstacles.clear();
    }
    void animateFrame() {
        long currentStepTime = SystemClock.uptimeMillis();
        step(currentStepTime);
        Handler handler = getHandler();
        if (handler != null) {
            handler.postAtTime(mAnimationRunnable, currentStepTime + ANIMATION_TIME_STEP);
            invalidate();
        }
    }
    private void step(long currentStepTime) {
        float tau = (currentStepTime - mLastStepTime) * 0.001f;
        mLastStepTime = currentStepTime;
        ensureInitialized();
        // Move the ship.
        mShip.accelerate(tau, mMaxShipThrust, mMaxShipSpeed);
        if (!mShip.step(tau)) {
            reset();
        }
        // Move the bullets.
        int numBullets = mBullets.size();
        for (int i = 0; i < numBullets; i++) {
            final Bullet bullet = mBullets.get(i);
            if (!bullet.step(tau)) {
                mBullets.remove(i);
                i -= 1;
                numBullets -= 1;
            }
        }
        // Move obstacles.
        int numObstacles = mObstacles.size();
        for (int i = 0; i < numObstacles; i++) {
            final Obstacle obstacle = mObstacles.get(i);
            if (!obstacle.step(tau)) {
                mObstacles.remove(i);
                i -= 1;
                numObstacles -= 1;
            }
        }
        // Check for collisions between bullets and obstacles.
        for (int i = 0; i < numBullets; i++) {
            final Bullet bullet = mBullets.get(i);
            for (int j = 0; j < numObstacles; j++) {
                final Obstacle obstacle = mObstacles.get(j);
                if (bullet.collidesWith(obstacle)) {
                    bullet.destroy();
                    obstacle.destroy();
                    break;
                }
            }
        }
        // Check for collisions between the ship and obstacles.
        for (int i = 0; i < numObstacles; i++) {
            final Obstacle obstacle = mObstacles.get(i);
            if (mShip.collidesWith(obstacle)) {
                mShip.destroy();
                obstacle.destroy();
                break;
            }
        }
        // Spawn more obstacles offscreen when needed.
        // Avoid putting them right on top of the ship.
        OuterLoop: while (mObstacles.size() < MAX_OBSTACLES) {
            final float minDistance = mShipSize * 4;
            float size = mRandom.nextFloat() * (mMaxObstacleSize - mMinObstacleSize)
                    + mMinObstacleSize;
            float positionX, positionY;
            int tries = 0;
            do {
                int edge = mRandom.nextInt(4);
                switch (edge) {
                    case 0:
                        positionX = -size;
                        positionY = mRandom.nextInt(getHeight());
                        break;
                    case 1:
                        positionX = getWidth() + size;
                        positionY = mRandom.nextInt(getHeight());
                        break;
                    case 2:
                        positionX = mRandom.nextInt(getWidth());
                        positionY = -size;
                        break;
                    default:
                        positionX = mRandom.nextInt(getWidth());
                        positionY = getHeight() + size;
                        break;
                }
                if (++tries > 10) {
                    break OuterLoop;
                }
            } while (mShip.distanceTo(positionX, positionY) < minDistance);
            float direction = mRandom.nextFloat() * (float) Math.PI * 2;
            float speed = mRandom.nextFloat() * (mMaxObstacleSpeed - mMinObstacleSpeed)
                    + mMinObstacleSpeed;
            float velocityX = (float) Math.cos(direction) * speed;
            float velocityY = (float) Math.sin(direction) * speed;
            Obstacle obstacle = new Obstacle();
            obstacle.setPosition(positionX, positionY);
            obstacle.setSize(size);
            obstacle.setVelocity(velocityX, velocityY);
            mObstacles.add(obstacle);
        }
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // Draw the ship.
        if (mShip != null) {
            mShip.draw(canvas);
        }
        // Draw bullets.
        int numBullets = mBullets.size();
        for (int i = 0; i < numBullets; i++) {
            final Bullet bullet = mBullets.get(i);
            bullet.draw(canvas);
        }
        // Draw obstacles.
        int numObstacles = mObstacles.size();
        for (int i = 0; i < numObstacles; i++) {
            final Obstacle obstacle = mObstacles.get(i);
            obstacle.draw(canvas);
        }
    }
    static float pythag(float x, float y) {
        return (float) Math.sqrt(x * x + y * y);
    }
    static int blend(float alpha, int from, int to) {
        return from + (int) ((to - from) * alpha);
    }
    static void setPaintARGBBlend(Paint paint, float alpha,
            int a1, int r1, int g1, int b1,
            int a2, int r2, int g2, int b2) {
        paint.setARGB(blend(alpha, a1, a2), blend(alpha, r1, r2),
                blend(alpha, g1, g2), blend(alpha, b1, b2));
    }
    private abstract class Sprite {
        protected float mPositionX;
        protected float mPositionY;
        protected float mVelocityX;
        protected float mVelocityY;
        protected float mSize;
        protected boolean mDestroyed;
        protected float mDestroyAnimProgress;
        public void setPosition(float x, float y) {
            mPositionX = x;
            mPositionY = y;
        }
        public void setVelocity(float x, float y) {
            mVelocityX = x;
            mVelocityY = y;
        }
        public void setSize(float size) {
            mSize = size;
        }
        public float distanceTo(float x, float y) {
            return pythag(mPositionX - x, mPositionY - y);
        }
        public float distanceTo(Sprite other) {
            return distanceTo(other.mPositionX, other.mPositionY);
        }
        public boolean collidesWith(Sprite other) {
            // Really bad collision detection.
            return !mDestroyed && !other.mDestroyed
                    && distanceTo(other) <= Math.max(mSize, other.mSize)
                            + Math.min(mSize, other.mSize) * 0.5f;
        }
        public boolean isDestroyed() {
            return mDestroyed;
        }
        public boolean step(float tau) {
            mPositionX += mVelocityX * tau;
            mPositionY += mVelocityY * tau;
            if (mDestroyed) {
                mDestroyAnimProgress += tau / getDestroyAnimDuration();
                if (mDestroyAnimProgress >= 1.0f) {
                    return false;
                }
            }
            return true;
        }
        public abstract void draw(Canvas canvas);
        public abstract float getDestroyAnimDuration();
        protected boolean isOutsidePlayfield() {
            final int width = GameView.this.getWidth();
            final int height = GameView.this.getHeight();
            return mPositionX < 0 || mPositionX >= width
                    || mPositionY < 0 || mPositionY >= height;
        }
        protected void wrapAtPlayfieldBoundary() {
            final int width = GameView.this.getWidth();
            final int height = GameView.this.getHeight();
            while (mPositionX <= -mSize) {
                mPositionX += width + mSize * 2;
            }
            while (mPositionX >= width + mSize) {
                mPositionX -= width + mSize * 2;
            }
            while (mPositionY <= -mSize) {
                mPositionY += height + mSize * 2;
            }
            while (mPositionY >= height + mSize) {
                mPositionY -= height + mSize * 2;
            }
        }
        public void destroy() {
            mDestroyed = true;
            step(0);
        }
    }
    private class Ship extends Sprite {
        private static final float CORNER_ANGLE = (float) Math.PI * 2 / 3;
        private static final float TO_DEGREES = (float) (180.0 / Math.PI);
        private float mHeadingX;
        private float mHeadingY;
        private float mHeadingAngle;
        private float mHeadingMagnitude;
        private final Paint mPaint;
        private final Path mPath;
        public Ship() {
            mPaint = new Paint();
            mPaint.setStyle(Style.FILL);
            setPosition(getWidth() * 0.5f, getHeight() * 0.5f);
            setVelocity(0, 0);
            setSize(mShipSize);
            mPath = new Path();
            mPath.moveTo(0, 0);
            mPath.lineTo((float)Math.cos(-CORNER_ANGLE) * mSize,
                    (float)Math.sin(-CORNER_ANGLE) * mSize);
            mPath.lineTo(mSize, 0);
            mPath.lineTo((float)Math.cos(CORNER_ANGLE) * mSize,
                    (float)Math.sin(CORNER_ANGLE) * mSize);
            mPath.lineTo(0, 0);
        }
        public void setHeadingX(float x) {
            mHeadingX = x;
            updateHeading();
        }
        public void setHeadingY(float y) {
            mHeadingY = y;
            updateHeading();
        }
        public void setHeading(float x, float y) {
            mHeadingX = x;
            mHeadingY = y;
            updateHeading();
        }
        private void updateHeading() {
            mHeadingMagnitude = pythag(mHeadingX, mHeadingY);
            if (mHeadingMagnitude > 0.1f) {
                mHeadingAngle = (float) Math.atan2(mHeadingY, mHeadingX);
            }
        }
        private float polarX(float radius) {
            return (float) Math.cos(mHeadingAngle) * radius;
        }
        private float polarY(float radius) {
            return (float) Math.sin(mHeadingAngle) * radius;
        }
        public float getBulletInitialX() {
            return mPositionX + polarX(mSize);
        }
        public float getBulletInitialY() {
            return mPositionY + polarY(mSize);
        }
        public float getBulletVelocityX(float relativeSpeed) {
            return mVelocityX + polarX(relativeSpeed);
        }
        public float getBulletVelocityY(float relativeSpeed) {
            return mVelocityY + polarY(relativeSpeed);
        }
        public void accelerate(float tau, float maxThrust, float maxSpeed) {
            final float thrust = mHeadingMagnitude * maxThrust;
            mVelocityX += polarX(thrust);
            mVelocityY += polarY(thrust);
            final float speed = pythag(mVelocityX, mVelocityY);
            if (speed > maxSpeed) {
                final float scale = maxSpeed / speed;
                mVelocityX = mVelocityX * scale;
                mVelocityY = mVelocityY * scale;
            }
        }
        @Override
        public boolean step(float tau) {
            if (!super.step(tau)) {
                return false;
            }
            wrapAtPlayfieldBoundary();
            return true;
        }
        public void draw(Canvas canvas) {
            setPaintARGBBlend(mPaint, mDestroyAnimProgress,
                    255, 63, 255, 63,
                    0, 255, 0, 0);
            canvas.save(Canvas.MATRIX_SAVE_FLAG);
            canvas.translate(mPositionX, mPositionY);
            canvas.rotate(mHeadingAngle * TO_DEGREES);
            canvas.drawPath(mPath, mPaint);
            canvas.restore();
        }
        @Override
        public float getDestroyAnimDuration() {
            return 1.0f;
        }
    }
    private class Bullet extends Sprite {
        private final Paint mPaint;
        public Bullet() {
            mPaint = new Paint();
            mPaint.setStyle(Style.FILL);
            setSize(mBulletSize);
        }
        @Override
        public boolean step(float tau) {
            if (!super.step(tau)) {
                return false;
            }
            return !isOutsidePlayfield();
        }
        public void draw(Canvas canvas) {
            setPaintARGBBlend(mPaint, mDestroyAnimProgress,
                    255, 255, 255, 0,
                    0, 255, 255, 255);
            canvas.drawCircle(mPositionX, mPositionY, mSize, mPaint);
        }
        @Override
        public float getDestroyAnimDuration() {
            return 0.125f;
        }
    }
    private class Obstacle extends Sprite {
        private final Paint mPaint;
        public Obstacle() {
            mPaint = new Paint();
            mPaint.setARGB(255, 127, 127, 255);
            mPaint.setStyle(Style.FILL);
        }
        @Override
        public boolean step(float tau) {
            if (!super.step(tau)) {
                return false;
            }
            wrapAtPlayfieldBoundary();
            return true;
        }
        public void draw(Canvas canvas) {
            setPaintARGBBlend(mPaint, mDestroyAnimProgress,
                    255, 127, 127, 255,
                    0, 255, 0, 0);
            canvas.drawCircle(mPositionX, mPositionY,
                    mSize * (1.0f - mDestroyAnimProgress), mPaint);
        }
        @Override
        public float getDestroyAnimDuration() {
            return 0.25f;
        }
    }
}
//layout/game_controller_input.xml



    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
            android:id="@+id/description"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/game_controller_input_description"
        android:padding="12dip" />
            android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1"
        android:padding="12dip">
                    android:id="@+id/summary"
            android:layout_width="0dip"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:padding="3dip">
        
                    android:id="@+id/game"
            android:layout_width="0dip"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="#000000"
            android:padding="3dip" />