diff options
12 files changed, 525 insertions, 0 deletions
diff --git a/samples/browseable/AccelerometerPlay/AndroidManifest.xml b/samples/browseable/AccelerometerPlay/AndroidManifest.xml new file mode 100644 index 000000000..3c3ec2743 --- /dev/null +++ b/samples/browseable/AccelerometerPlay/AndroidManifest.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + android:versionCode="1" + android:versionName="1.0" package="com.example.android.accelerometerplay"> + <application android:icon="@mipmap/ic_launcher" android:label="@string/app_name"> + <activity android:name=".AccelerometerPlayActivity" + android:label="@string/app_name" + android:screenOrientation="portrait" + android:theme="@android:style/Theme.NoTitleBar.Fullscreen"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + </application> + + +<uses-sdk android:minSdkVersion="5"></uses-sdk> +<uses-permission android:name="android.permission.VIBRATE"></uses-permission> + +<uses-permission android:name="android.permission.WAKE_LOCK"></uses-permission> + +</manifest> diff --git a/samples/browseable/AccelerometerPlay/_index.jd b/samples/browseable/AccelerometerPlay/_index.jd new file mode 100644 index 000000000..5e09e2d14 --- /dev/null +++ b/samples/browseable/AccelerometerPlay/_index.jd @@ -0,0 +1,15 @@ + +page.tags="AccelerometerPlay" +sample.group=Sensors +@jd:body + +<p> + + <p>This sample demonstrates how to use an accelerometer sensor as input for + a physics-based view. The input from the accelerometer is used to simulate a + virtual surface, and a number of free-moving objects placed on top of it.</p> + + <p>Any effects from the device's acceleration vector (including both gravity and + temporary movement) will be translated to the on-screen particles.</p> + + </p> diff --git a/samples/browseable/AccelerometerPlay/res/drawable-hdpi/ball.png b/samples/browseable/AccelerometerPlay/res/drawable-hdpi/ball.png Binary files differnew file mode 100644 index 000000000..e79e4d615 --- /dev/null +++ b/samples/browseable/AccelerometerPlay/res/drawable-hdpi/ball.png diff --git a/samples/browseable/AccelerometerPlay/res/drawable-hdpi/wood.jpg b/samples/browseable/AccelerometerPlay/res/drawable-hdpi/wood.jpg Binary files differnew file mode 100644 index 000000000..883f491d9 --- /dev/null +++ b/samples/browseable/AccelerometerPlay/res/drawable-hdpi/wood.jpg diff --git a/samples/browseable/AccelerometerPlay/res/layout/main.xml b/samples/browseable/AccelerometerPlay/res/layout/main.xml new file mode 100644 index 000000000..c69b22294 --- /dev/null +++ b/samples/browseable/AccelerometerPlay/res/layout/main.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 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. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:background="@drawable/wood" + > +</FrameLayout> diff --git a/samples/browseable/AccelerometerPlay/res/mipmap-hdpi/ic_launcher.png b/samples/browseable/AccelerometerPlay/res/mipmap-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000..800c55629 --- /dev/null +++ b/samples/browseable/AccelerometerPlay/res/mipmap-hdpi/ic_launcher.png diff --git a/samples/browseable/AccelerometerPlay/res/mipmap-mdpi/ic_launcher.png b/samples/browseable/AccelerometerPlay/res/mipmap-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000..1d6e9e55f --- /dev/null +++ b/samples/browseable/AccelerometerPlay/res/mipmap-mdpi/ic_launcher.png diff --git a/samples/browseable/AccelerometerPlay/res/mipmap-xhdpi/ic_launcher.png b/samples/browseable/AccelerometerPlay/res/mipmap-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000..2989356b5 --- /dev/null +++ b/samples/browseable/AccelerometerPlay/res/mipmap-xhdpi/ic_launcher.png diff --git a/samples/browseable/AccelerometerPlay/res/mipmap-xxhdpi/ic_launcher.png b/samples/browseable/AccelerometerPlay/res/mipmap-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000..56b87a28d --- /dev/null +++ b/samples/browseable/AccelerometerPlay/res/mipmap-xxhdpi/ic_launcher.png diff --git a/samples/browseable/AccelerometerPlay/res/mipmap-xxxhdpi/ic_launcher.png b/samples/browseable/AccelerometerPlay/res/mipmap-xxxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000..500543319 --- /dev/null +++ b/samples/browseable/AccelerometerPlay/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/samples/browseable/AccelerometerPlay/res/values/strings.xml b/samples/browseable/AccelerometerPlay/res/values/strings.xml new file mode 100644 index 000000000..6e3785e94 --- /dev/null +++ b/samples/browseable/AccelerometerPlay/res/values/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 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. +--> + +<resources> + <string name="app_name">AccelerometerPlay</string> +</resources> diff --git a/samples/browseable/AccelerometerPlay/src/com.example.android.accelerometerplay/AccelerometerPlayActivity.java b/samples/browseable/AccelerometerPlay/src/com.example.android.accelerometerplay/AccelerometerPlayActivity.java new file mode 100644 index 000000000..b15685261 --- /dev/null +++ b/samples/browseable/AccelerometerPlay/src/com.example.android.accelerometerplay/AccelerometerPlayActivity.java @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2010 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.accelerometerplay; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.BitmapFactory.Options; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Build; +import android.os.Bundle; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.Surface; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.FrameLayout; + +/** + * This is an example of using the accelerometer to integrate the device's + * acceleration to a position using the Verlet method. This is illustrated with + * a very simple particle system comprised of a few iron balls freely moving on + * an inclined wooden table. The inclination of the virtual table is controlled + * by the device's accelerometer. + * + * @see SensorManager + * @see SensorEvent + * @see Sensor + */ + +public class AccelerometerPlayActivity extends Activity { + + private SimulationView mSimulationView; + private SensorManager mSensorManager; + private PowerManager mPowerManager; + private WindowManager mWindowManager; + private Display mDisplay; + private WakeLock mWakeLock; + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Get an instance of the SensorManager + mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); + + // Get an instance of the PowerManager + mPowerManager = (PowerManager) getSystemService(POWER_SERVICE); + + // Get an instance of the WindowManager + mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); + mDisplay = mWindowManager.getDefaultDisplay(); + + // Create a bright wake lock + mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, getClass() + .getName()); + + // instantiate our simulation view and set it as the activity's content + mSimulationView = new SimulationView(this); + mSimulationView.setBackgroundResource(R.drawable.wood); + setContentView(mSimulationView); + } + + @Override + protected void onResume() { + super.onResume(); + /* + * when the activity is resumed, we acquire a wake-lock so that the + * screen stays on, since the user will likely not be fiddling with the + * screen or buttons. + */ + mWakeLock.acquire(); + + // Start the simulation + mSimulationView.startSimulation(); + } + + @Override + protected void onPause() { + super.onPause(); + /* + * When the activity is paused, we make sure to stop the simulation, + * release our sensor resources and wake locks + */ + + // Stop the simulation + mSimulationView.stopSimulation(); + + // and release our wake-lock + mWakeLock.release(); + } + + class SimulationView extends FrameLayout implements SensorEventListener { + // diameter of the balls in meters + private static final float sBallDiameter = 0.004f; + private static final float sBallDiameter2 = sBallDiameter * sBallDiameter; + + private final int mDstWidth; + private final int mDstHeight; + + private Sensor mAccelerometer; + private long mLastT; + + private float mXDpi; + private float mYDpi; + private float mMetersToPixelsX; + private float mMetersToPixelsY; + private float mXOrigin; + private float mYOrigin; + private float mSensorX; + private float mSensorY; + private float mHorizontalBound; + private float mVerticalBound; + private final ParticleSystem mParticleSystem; + /* + * Each of our particle holds its previous and current position, its + * acceleration. for added realism each particle has its own friction + * coefficient. + */ + class Particle extends View { + private float mPosX = (float) Math.random(); + private float mPosY = (float) Math.random(); + private float mVelX; + private float mVelY; + + public Particle(Context context) { + super(context); + } + + public Particle(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public Particle(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public Particle(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public void computePhysics(float sx, float sy, float dT) { + + final float ax = -sx/5; + final float ay = -sy/5; + + mPosX += mVelX * dT + ax * dT * dT / 2; + mPosY += mVelY * dT + ay * dT * dT / 2; + + mVelX += ax * dT; + mVelY += ay * dT; + } + + /* + * Resolving constraints and collisions with the Verlet integrator + * can be very simple, we simply need to move a colliding or + * constrained particle in such way that the constraint is + * satisfied. + */ + public void resolveCollisionWithBounds() { + final float xmax = mHorizontalBound; + final float ymax = mVerticalBound; + final float x = mPosX; + final float y = mPosY; + if (x > xmax) { + mPosX = xmax; + mVelX = 0; + } else if (x < -xmax) { + mPosX = -xmax; + mVelX = 0; + } + if (y > ymax) { + mPosY = ymax; + mVelY = 0; + } else if (y < -ymax) { + mPosY = -ymax; + mVelY = 0; + } + } + } + + /* + * A particle system is just a collection of particles + */ + class ParticleSystem { + static final int NUM_PARTICLES = 5; + private Particle mBalls[] = new Particle[NUM_PARTICLES]; + + ParticleSystem() { + /* + * Initially our particles have no speed or acceleration + */ + for (int i = 0; i < mBalls.length; i++) { + mBalls[i] = new Particle(getContext()); + mBalls[i].setBackgroundResource(R.drawable.ball); + mBalls[i].setLayerType(LAYER_TYPE_HARDWARE, null); + addView(mBalls[i], new ViewGroup.LayoutParams(mDstWidth, mDstHeight)); + } + } + + /* + * Update the position of each particle in the system using the + * Verlet integrator. + */ + private void updatePositions(float sx, float sy, long timestamp) { + final long t = timestamp; + if (mLastT != 0) { + final float dT = (float) (t - mLastT) / 1000.f /** (1.0f / 1000000000.0f)*/; + final int count = mBalls.length; + for (int i = 0; i < count; i++) { + Particle ball = mBalls[i]; + ball.computePhysics(sx, sy, dT); + } + } + mLastT = t; + } + + /* + * Performs one iteration of the simulation. First updating the + * position of all the particles and resolving the constraints and + * collisions. + */ + public void update(float sx, float sy, long now) { + // update the system's positions + updatePositions(sx, sy, now); + + // We do no more than a limited number of iterations + final int NUM_MAX_ITERATIONS = 10; + + /* + * Resolve collisions, each particle is tested against every + * other particle for collision. If a collision is detected the + * particle is moved away using a virtual spring of infinite + * stiffness. + */ + boolean more = true; + final int count = mBalls.length; + for (int k = 0; k < NUM_MAX_ITERATIONS && more; k++) { + more = false; + for (int i = 0; i < count; i++) { + Particle curr = mBalls[i]; + for (int j = i + 1; j < count; j++) { + Particle ball = mBalls[j]; + float dx = ball.mPosX - curr.mPosX; + float dy = ball.mPosY - curr.mPosY; + float dd = dx * dx + dy * dy; + // Check for collisions + if (dd <= sBallDiameter2) { + /* + * add a little bit of entropy, after nothing is + * perfect in the universe. + */ + dx += ((float) Math.random() - 0.5f) * 0.0001f; + dy += ((float) Math.random() - 0.5f) * 0.0001f; + dd = dx * dx + dy * dy; + // simulate the spring + final float d = (float) Math.sqrt(dd); + final float c = (0.5f * (sBallDiameter - d)) / d; + final float effectX = dx * c; + final float effectY = dy * c; + curr.mPosX -= effectX; + curr.mPosY -= effectY; + ball.mPosX += effectX; + ball.mPosY += effectY; + more = true; + } + } + curr.resolveCollisionWithBounds(); + } + } + } + + public int getParticleCount() { + return mBalls.length; + } + + public float getPosX(int i) { + return mBalls[i].mPosX; + } + + public float getPosY(int i) { + return mBalls[i].mPosY; + } + } + + public void startSimulation() { + /* + * It is not necessary to get accelerometer events at a very high + * rate, by using a slower rate (SENSOR_DELAY_UI), we get an + * automatic low-pass filter, which "extracts" the gravity component + * of the acceleration. As an added benefit, we use less power and + * CPU resources. + */ + mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME); + } + + public void stopSimulation() { + mSensorManager.unregisterListener(this); + } + + public SimulationView(Context context) { + super(context); + mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + + DisplayMetrics metrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(metrics); + mXDpi = metrics.xdpi; + mYDpi = metrics.ydpi; + mMetersToPixelsX = mXDpi / 0.0254f; + mMetersToPixelsY = mYDpi / 0.0254f; + + // rescale the ball so it's about 0.5 cm on screen + mDstWidth = (int) (sBallDiameter * mMetersToPixelsX + 0.5f); + mDstHeight = (int) (sBallDiameter * mMetersToPixelsY + 0.5f); + mParticleSystem = new ParticleSystem(); + + Options opts = new Options(); + opts.inDither = true; + opts.inPreferredConfig = Bitmap.Config.RGB_565; + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + // compute the origin of the screen relative to the origin of + // the bitmap + mXOrigin = (w - mDstWidth) * 0.5f; + mYOrigin = (h - mDstHeight) * 0.5f; + mHorizontalBound = ((w / mMetersToPixelsX - sBallDiameter) * 0.5f); + mVerticalBound = ((h / mMetersToPixelsY - sBallDiameter) * 0.5f); + } + + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) + return; + /* + * record the accelerometer data, the event's timestamp as well as + * the current time. The latter is needed so we can calculate the + * "present" time during rendering. In this application, we need to + * take into account how the screen is rotated with respect to the + * sensors (which always return data in a coordinate space aligned + * to with the screen in its native orientation). + */ + + switch (mDisplay.getRotation()) { + case Surface.ROTATION_0: + mSensorX = event.values[0]; + mSensorY = event.values[1]; + break; + case Surface.ROTATION_90: + mSensorX = -event.values[1]; + mSensorY = event.values[0]; + break; + case Surface.ROTATION_180: + mSensorX = -event.values[0]; + mSensorY = -event.values[1]; + break; + case Surface.ROTATION_270: + mSensorX = event.values[1]; + mSensorY = -event.values[0]; + break; + } + } + + @Override + protected void onDraw(Canvas canvas) { + /* + * Compute the new position of our object, based on accelerometer + * data and present time. + */ + final ParticleSystem particleSystem = mParticleSystem; + final long now = System.currentTimeMillis(); + final float sx = mSensorX; + final float sy = mSensorY; + + particleSystem.update(sx, sy, now); + + final float xc = mXOrigin; + final float yc = mYOrigin; + final float xs = mMetersToPixelsX; + final float ys = mMetersToPixelsY; + final int count = particleSystem.getParticleCount(); + for (int i = 0; i < count; i++) { + /* + * We transform the canvas so that the coordinate system matches + * the sensors coordinate system with the origin in the center + * of the screen and the unit is the meter. + */ + final float x = xc + particleSystem.getPosX(i) * xs; + final float y = yc - particleSystem.getPosY(i) * ys; + particleSystem.mBalls[i].setTranslationX(x); + particleSystem.mBalls[i].setTranslationY(y); + } + + // and make sure to redraw asap + invalidate(); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + } +} |