Home | History | Annotate | Download | only in com.example.android.accelerometerplay
      1 /*
      2  * Copyright (C) 2010 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.accelerometerplay;
     18 
     19 import android.annotation.TargetApi;
     20 import android.app.Activity;
     21 import android.content.Context;
     22 import android.graphics.Bitmap;
     23 import android.graphics.Canvas;
     24 import android.graphics.BitmapFactory.Options;
     25 import android.hardware.Sensor;
     26 import android.hardware.SensorEvent;
     27 import android.hardware.SensorEventListener;
     28 import android.hardware.SensorManager;
     29 import android.os.Build;
     30 import android.os.Bundle;
     31 import android.os.PowerManager;
     32 import android.os.PowerManager.WakeLock;
     33 import android.util.AttributeSet;
     34 import android.util.DisplayMetrics;
     35 import android.view.Display;
     36 import android.view.Surface;
     37 import android.view.View;
     38 import android.view.ViewGroup;
     39 import android.view.WindowManager;
     40 import android.widget.FrameLayout;
     41 
     42 /**
     43  * This is an example of using the accelerometer to integrate the device's
     44  * acceleration to a position using the Verlet method. This is illustrated with
     45  * a very simple particle system comprised of a few iron balls freely moving on
     46  * an inclined wooden table. The inclination of the virtual table is controlled
     47  * by the device's accelerometer.
     48  *
     49  * @see SensorManager
     50  * @see SensorEvent
     51  * @see Sensor
     52  */
     53 
     54 public class AccelerometerPlayActivity extends Activity {
     55 
     56     private SimulationView mSimulationView;
     57     private SensorManager mSensorManager;
     58     private PowerManager mPowerManager;
     59     private WindowManager mWindowManager;
     60     private Display mDisplay;
     61     private WakeLock mWakeLock;
     62 
     63     /** Called when the activity is first created. */
     64     @Override
     65     public void onCreate(Bundle savedInstanceState) {
     66         super.onCreate(savedInstanceState);
     67 
     68         // Get an instance of the SensorManager
     69         mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
     70 
     71         // Get an instance of the PowerManager
     72         mPowerManager = (PowerManager) getSystemService(POWER_SERVICE);
     73 
     74         // Get an instance of the WindowManager
     75         mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
     76         mDisplay = mWindowManager.getDefaultDisplay();
     77 
     78         // Create a bright wake lock
     79         mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, getClass()
     80                 .getName());
     81 
     82         // instantiate our simulation view and set it as the activity's content
     83         mSimulationView = new SimulationView(this);
     84         mSimulationView.setBackgroundResource(R.drawable.wood);
     85         setContentView(mSimulationView);
     86     }
     87 
     88     @Override
     89     protected void onResume() {
     90         super.onResume();
     91         /*
     92          * when the activity is resumed, we acquire a wake-lock so that the
     93          * screen stays on, since the user will likely not be fiddling with the
     94          * screen or buttons.
     95          */
     96         mWakeLock.acquire();
     97 
     98         // Start the simulation
     99         mSimulationView.startSimulation();
    100     }
    101 
    102     @Override
    103     protected void onPause() {
    104         super.onPause();
    105         /*
    106          * When the activity is paused, we make sure to stop the simulation,
    107          * release our sensor resources and wake locks
    108          */
    109 
    110         // Stop the simulation
    111         mSimulationView.stopSimulation();
    112 
    113         // and release our wake-lock
    114         mWakeLock.release();
    115     }
    116 
    117     class SimulationView extends FrameLayout implements SensorEventListener {
    118         // diameter of the balls in meters
    119         private static final float sBallDiameter = 0.004f;
    120         private static final float sBallDiameter2 = sBallDiameter * sBallDiameter;
    121 
    122         private final int mDstWidth;
    123         private final int mDstHeight;
    124 
    125         private Sensor mAccelerometer;
    126         private long mLastT;
    127 
    128         private float mXDpi;
    129         private float mYDpi;
    130         private float mMetersToPixelsX;
    131         private float mMetersToPixelsY;
    132         private float mXOrigin;
    133         private float mYOrigin;
    134         private float mSensorX;
    135         private float mSensorY;
    136         private float mHorizontalBound;
    137         private float mVerticalBound;
    138         private final ParticleSystem mParticleSystem;
    139         /*
    140          * Each of our particle holds its previous and current position, its
    141          * acceleration. for added realism each particle has its own friction
    142          * coefficient.
    143          */
    144         class Particle extends View {
    145             private float mPosX = (float) Math.random();
    146             private float mPosY = (float) Math.random();
    147             private float mVelX;
    148             private float mVelY;
    149 
    150             public Particle(Context context) {
    151                 super(context);
    152             }
    153 
    154             public Particle(Context context, AttributeSet attrs) {
    155                 super(context, attrs);
    156             }
    157 
    158             public Particle(Context context, AttributeSet attrs, int defStyleAttr) {
    159                 super(context, attrs, defStyleAttr);
    160             }
    161 
    162             @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    163             public Particle(Context context, AttributeSet attrs, int defStyleAttr,
    164                             int defStyleRes) {
    165                 super(context, attrs, defStyleAttr, defStyleRes);
    166             }
    167 
    168             public void computePhysics(float sx, float sy, float dT) {
    169 
    170                 final float ax = -sx/5;
    171                 final float ay = -sy/5;
    172 
    173                 mPosX += mVelX * dT + ax * dT * dT / 2;
    174                 mPosY += mVelY * dT + ay * dT * dT / 2;
    175 
    176                 mVelX += ax * dT;
    177                 mVelY += ay * dT;
    178             }
    179 
    180             /*
    181              * Resolving constraints and collisions with the Verlet integrator
    182              * can be very simple, we simply need to move a colliding or
    183              * constrained particle in such way that the constraint is
    184              * satisfied.
    185              */
    186             public void resolveCollisionWithBounds() {
    187                 final float xmax = mHorizontalBound;
    188                 final float ymax = mVerticalBound;
    189                 final float x = mPosX;
    190                 final float y = mPosY;
    191                 if (x > xmax) {
    192                     mPosX = xmax;
    193                     mVelX = 0;
    194                 } else if (x < -xmax) {
    195                     mPosX = -xmax;
    196                     mVelX = 0;
    197                 }
    198                 if (y > ymax) {
    199                     mPosY = ymax;
    200                     mVelY = 0;
    201                 } else if (y < -ymax) {
    202                     mPosY = -ymax;
    203                     mVelY = 0;
    204                 }
    205             }
    206         }
    207 
    208         /*
    209          * A particle system is just a collection of particles
    210          */
    211         class ParticleSystem {
    212             static final int NUM_PARTICLES = 5;
    213             private Particle mBalls[] = new Particle[NUM_PARTICLES];
    214 
    215             ParticleSystem() {
    216                 /*
    217                  * Initially our particles have no speed or acceleration
    218                  */
    219                 for (int i = 0; i < mBalls.length; i++) {
    220                     mBalls[i] = new Particle(getContext());
    221                     mBalls[i].setBackgroundResource(R.drawable.ball);
    222                     mBalls[i].setLayerType(LAYER_TYPE_HARDWARE, null);
    223                     addView(mBalls[i], new ViewGroup.LayoutParams(mDstWidth, mDstHeight));
    224                 }
    225             }
    226 
    227             /*
    228              * Update the position of each particle in the system using the
    229              * Verlet integrator.
    230              */
    231             private void updatePositions(float sx, float sy, long timestamp) {
    232                 final long t = timestamp;
    233                 if (mLastT != 0) {
    234                     final float dT = (float) (t - mLastT) / 1000.f /** (1.0f / 1000000000.0f)*/;
    235                         final int count = mBalls.length;
    236                         for (int i = 0; i < count; i++) {
    237                             Particle ball = mBalls[i];
    238                             ball.computePhysics(sx, sy, dT);
    239                         }
    240                 }
    241                 mLastT = t;
    242             }
    243 
    244             /*
    245              * Performs one iteration of the simulation. First updating the
    246              * position of all the particles and resolving the constraints and
    247              * collisions.
    248              */
    249             public void update(float sx, float sy, long now) {
    250                 // update the system's positions
    251                 updatePositions(sx, sy, now);
    252 
    253                 // We do no more than a limited number of iterations
    254                 final int NUM_MAX_ITERATIONS = 10;
    255 
    256                 /*
    257                  * Resolve collisions, each particle is tested against every
    258                  * other particle for collision. If a collision is detected the
    259                  * particle is moved away using a virtual spring of infinite
    260                  * stiffness.
    261                  */
    262                 boolean more = true;
    263                 final int count = mBalls.length;
    264                 for (int k = 0; k < NUM_MAX_ITERATIONS && more; k++) {
    265                     more = false;
    266                     for (int i = 0; i < count; i++) {
    267                         Particle curr = mBalls[i];
    268                         for (int j = i + 1; j < count; j++) {
    269                             Particle ball = mBalls[j];
    270                             float dx = ball.mPosX - curr.mPosX;
    271                             float dy = ball.mPosY - curr.mPosY;
    272                             float dd = dx * dx + dy * dy;
    273                             // Check for collisions
    274                             if (dd <= sBallDiameter2) {
    275                                 /*
    276                                  * add a little bit of entropy, after nothing is
    277                                  * perfect in the universe.
    278                                  */
    279                                 dx += ((float) Math.random() - 0.5f) * 0.0001f;
    280                                 dy += ((float) Math.random() - 0.5f) * 0.0001f;
    281                                 dd = dx * dx + dy * dy;
    282                                 // simulate the spring
    283                                 final float d = (float) Math.sqrt(dd);
    284                                 final float c = (0.5f * (sBallDiameter - d)) / d;
    285                                 final float effectX = dx * c;
    286                                 final float effectY = dy * c;
    287                                 curr.mPosX -= effectX;
    288                                 curr.mPosY -= effectY;
    289                                 ball.mPosX += effectX;
    290                                 ball.mPosY += effectY;
    291                                 more = true;
    292                             }
    293                         }
    294                         curr.resolveCollisionWithBounds();
    295                     }
    296                 }
    297             }
    298 
    299             public int getParticleCount() {
    300                 return mBalls.length;
    301             }
    302 
    303             public float getPosX(int i) {
    304                 return mBalls[i].mPosX;
    305             }
    306 
    307             public float getPosY(int i) {
    308                 return mBalls[i].mPosY;
    309             }
    310         }
    311 
    312         public void startSimulation() {
    313             /*
    314              * It is not necessary to get accelerometer events at a very high
    315              * rate, by using a slower rate (SENSOR_DELAY_UI), we get an
    316              * automatic low-pass filter, which "extracts" the gravity component
    317              * of the acceleration. As an added benefit, we use less power and
    318              * CPU resources.
    319              */
    320             mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME);
    321         }
    322 
    323         public void stopSimulation() {
    324             mSensorManager.unregisterListener(this);
    325         }
    326 
    327         public SimulationView(Context context) {
    328             super(context);
    329             mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    330 
    331             DisplayMetrics metrics = new DisplayMetrics();
    332             getWindowManager().getDefaultDisplay().getMetrics(metrics);
    333             mXDpi = metrics.xdpi;
    334             mYDpi = metrics.ydpi;
    335             mMetersToPixelsX = mXDpi / 0.0254f;
    336             mMetersToPixelsY = mYDpi / 0.0254f;
    337 
    338             // rescale the ball so it's about 0.5 cm on screen
    339             mDstWidth = (int) (sBallDiameter * mMetersToPixelsX + 0.5f);
    340             mDstHeight = (int) (sBallDiameter * mMetersToPixelsY + 0.5f);
    341             mParticleSystem = new ParticleSystem();
    342 
    343             Options opts = new Options();
    344             opts.inDither = true;
    345             opts.inPreferredConfig = Bitmap.Config.RGB_565;
    346         }
    347 
    348         @Override
    349         protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    350             // compute the origin of the screen relative to the origin of
    351             // the bitmap
    352             mXOrigin = (w - mDstWidth) * 0.5f;
    353             mYOrigin = (h - mDstHeight) * 0.5f;
    354             mHorizontalBound = ((w / mMetersToPixelsX - sBallDiameter) * 0.5f);
    355             mVerticalBound = ((h / mMetersToPixelsY - sBallDiameter) * 0.5f);
    356         }
    357 
    358         @Override
    359         public void onSensorChanged(SensorEvent event) {
    360             if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER)
    361                 return;
    362             /*
    363              * record the accelerometer data, the event's timestamp as well as
    364              * the current time. The latter is needed so we can calculate the
    365              * "present" time during rendering. In this application, we need to
    366              * take into account how the screen is rotated with respect to the
    367              * sensors (which always return data in a coordinate space aligned
    368              * to with the screen in its native orientation).
    369              */
    370 
    371             switch (mDisplay.getRotation()) {
    372                 case Surface.ROTATION_0:
    373                     mSensorX = event.values[0];
    374                     mSensorY = event.values[1];
    375                     break;
    376                 case Surface.ROTATION_90:
    377                     mSensorX = -event.values[1];
    378                     mSensorY = event.values[0];
    379                     break;
    380                 case Surface.ROTATION_180:
    381                     mSensorX = -event.values[0];
    382                     mSensorY = -event.values[1];
    383                     break;
    384                 case Surface.ROTATION_270:
    385                     mSensorX = event.values[1];
    386                     mSensorY = -event.values[0];
    387                     break;
    388             }
    389         }
    390 
    391         @Override
    392         protected void onDraw(Canvas canvas) {
    393             /*
    394              * Compute the new position of our object, based on accelerometer
    395              * data and present time.
    396              */
    397             final ParticleSystem particleSystem = mParticleSystem;
    398             final long now = System.currentTimeMillis();
    399             final float sx = mSensorX;
    400             final float sy = mSensorY;
    401 
    402             particleSystem.update(sx, sy, now);
    403 
    404             final float xc = mXOrigin;
    405             final float yc = mYOrigin;
    406             final float xs = mMetersToPixelsX;
    407             final float ys = mMetersToPixelsY;
    408             final int count = particleSystem.getParticleCount();
    409             for (int i = 0; i < count; i++) {
    410                 /*
    411                  * We transform the canvas so that the coordinate system matches
    412                  * the sensors coordinate system with the origin in the center
    413                  * of the screen and the unit is the meter.
    414                  */
    415                 final float x = xc + particleSystem.getPosX(i) * xs;
    416                 final float y = yc - particleSystem.getPosY(i) * ys;
    417                 particleSystem.mBalls[i].setTranslationX(x);
    418                 particleSystem.mBalls[i].setTranslationY(y);
    419             }
    420 
    421             // and make sure to redraw asap
    422             invalidate();
    423         }
    424 
    425         @Override
    426         public void onAccuracyChanged(Sensor sensor, int accuracy) {
    427         }
    428     }
    429 }
    430