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
onCreate(Bundle savedInstanceState)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
onResume()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
onPause()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 
Particle(Context context)150             public Particle(Context context) {
151                 super(context);
152             }
153 
Particle(Context context, AttributeSet attrs)154             public Particle(Context context, AttributeSet attrs) {
155                 super(context, attrs);
156             }
157 
Particle(Context context, AttributeSet attrs, int defStyleAttr)158             public Particle(Context context, AttributeSet attrs, int defStyleAttr) {
159                 super(context, attrs, defStyleAttr);
160             }
161 
162             @TargetApi(Build.VERSION_CODES.LOLLIPOP)
Particle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)163             public Particle(Context context, AttributeSet attrs, int defStyleAttr,
164                             int defStyleRes) {
165                 super(context, attrs, defStyleAttr, defStyleRes);
166             }
167 
computePhysics(float sx, float sy, float dT)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              */
resolveCollisionWithBounds()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 
ParticleSystem()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              */
updatePositions(float sx, float sy, long timestamp)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              */
update(float sx, float sy, long now)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 
getParticleCount()299             public int getParticleCount() {
300                 return mBalls.length;
301             }
302 
getPosX(int i)303             public float getPosX(int i) {
304                 return mBalls[i].mPosX;
305             }
306 
getPosY(int i)307             public float getPosY(int i) {
308                 return mBalls[i].mPosY;
309             }
310         }
311 
startSimulation()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 
stopSimulation()323         public void stopSimulation() {
324             mSensorManager.unregisterListener(this);
325         }
326 
SimulationView(Context context)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
onSizeChanged(int w, int h, int oldw, int oldh)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
onSensorChanged(SensorEvent event)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
onDraw(Canvas canvas)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
onAccuracyChanged(Sensor sensor, int accuracy)426         public void onAccuracyChanged(Sensor sensor, int accuracy) {
427         }
428     }
429 }
430