Home | History | Annotate | Download | only in app
      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.android.gallery3d.app;
     18 
     19 import android.content.Context;
     20 import android.hardware.Sensor;
     21 import android.hardware.SensorEvent;
     22 import android.hardware.SensorEventListener;
     23 import android.hardware.SensorManager;
     24 import android.os.SystemClock;
     25 import android.util.FloatMath;
     26 import android.view.Display;
     27 import android.view.Surface;
     28 import android.view.WindowManager;
     29 
     30 import com.android.gallery3d.common.Utils;
     31 import com.android.gallery3d.util.GalleryUtils;
     32 
     33 public class EyePosition {
     34     @SuppressWarnings("unused")
     35     private static final String TAG = "EyePosition";
     36 
     37     public interface EyePositionListener {
     38         public void onEyePositionChanged(float x, float y, float z);
     39     }
     40 
     41     private static final float GYROSCOPE_THRESHOLD = 0.15f;
     42     private static final float GYROSCOPE_LIMIT = 10f;
     43     private static final int GYROSCOPE_SETTLE_DOWN = 15;
     44     private static final float GYROSCOPE_RESTORE_FACTOR = 0.995f;
     45 
     46     private static final float USER_ANGEL = (float) Math.toRadians(10);
     47     private static final float USER_ANGEL_COS = FloatMath.cos(USER_ANGEL);
     48     private static final float USER_ANGEL_SIN = FloatMath.sin(USER_ANGEL);
     49     private static final float MAX_VIEW_RANGE = (float) 0.5;
     50     private static final int NOT_STARTED = -1;
     51 
     52     private static final float USER_DISTANCE_METER = 0.3f;
     53 
     54     private Context mContext;
     55     private EyePositionListener mListener;
     56     private Display mDisplay;
     57     // The eyes' position of the user, the origin is at the center of the
     58     // device and the unit is in pixels.
     59     private float mX;
     60     private float mY;
     61     private float mZ;
     62 
     63     private final float mUserDistance; // in pixel
     64     private final float mLimit;
     65     private long mStartTime = NOT_STARTED;
     66     private Sensor mSensor;
     67     private PositionListener mPositionListener = new PositionListener();
     68 
     69     private int mGyroscopeCountdown = 0;
     70 
     71     public EyePosition(Context context, EyePositionListener listener) {
     72         mContext = context;
     73         mListener = listener;
     74         mUserDistance = GalleryUtils.meterToPixel(USER_DISTANCE_METER);
     75         mLimit = mUserDistance * MAX_VIEW_RANGE;
     76 
     77         WindowManager wManager = (WindowManager) mContext
     78                 .getSystemService(Context.WINDOW_SERVICE);
     79         mDisplay = wManager.getDefaultDisplay();
     80 
     81         // The 3D effect where the photo albums fan out in 3D based on angle
     82         // of device tilt is currently disabled.
     83 /*
     84         SensorManager sManager = (SensorManager) mContext
     85                 .getSystemService(Context.SENSOR_SERVICE);
     86         mSensor = sManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
     87         if (mSensor == null) {
     88             Log.w(TAG, "no gyroscope, use accelerometer instead");
     89             mSensor = sManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
     90         }
     91         if (mSensor == null) {
     92             Log.w(TAG, "no sensor available");
     93         }
     94 */
     95     }
     96 
     97     public void resetPosition() {
     98         mStartTime = NOT_STARTED;
     99         mX = mY = 0;
    100         mZ = -mUserDistance;
    101         mListener.onEyePositionChanged(mX, mY, mZ);
    102     }
    103 
    104     /*
    105      * We assume the user is at the following position
    106      *
    107      *              /|\  user's eye
    108      *               |   /
    109      *   -G(gravity) |  /
    110      *               |_/
    111      *             / |/_____\ -Y (-y direction of device)
    112      *     user angel
    113      */
    114     private void onAccelerometerChanged(float gx, float gy, float gz) {
    115 
    116         float x = gx, y = gy, z = gz;
    117 
    118         switch (mDisplay.getRotation()) {
    119             case Surface.ROTATION_90: x = -gy; y= gx; break;
    120             case Surface.ROTATION_180: x = -gx; y = -gy; break;
    121             case Surface.ROTATION_270: x = gy; y = -gx; break;
    122         }
    123 
    124         float temp = x * x + y * y + z * z;
    125         float t = -y /temp;
    126 
    127         float tx = t * x;
    128         float ty = -1 + t * y;
    129         float tz = t * z;
    130 
    131         float length = FloatMath.sqrt(tx * tx + ty * ty + tz * tz);
    132         float glength = FloatMath.sqrt(temp);
    133 
    134         mX = Utils.clamp((x * USER_ANGEL_COS / glength
    135                 + tx * USER_ANGEL_SIN / length) * mUserDistance,
    136                 -mLimit, mLimit);
    137         mY = -Utils.clamp((y * USER_ANGEL_COS / glength
    138                 + ty * USER_ANGEL_SIN / length) * mUserDistance,
    139                 -mLimit, mLimit);
    140         mZ = -FloatMath.sqrt(
    141                 mUserDistance * mUserDistance - mX * mX - mY * mY);
    142         mListener.onEyePositionChanged(mX, mY, mZ);
    143     }
    144 
    145     private void onGyroscopeChanged(float gx, float gy, float gz) {
    146         long now = SystemClock.elapsedRealtime();
    147         float distance = (gx > 0 ? gx : -gx) + (gy > 0 ? gy : - gy);
    148         if (distance < GYROSCOPE_THRESHOLD
    149                 || distance > GYROSCOPE_LIMIT || mGyroscopeCountdown > 0) {
    150             --mGyroscopeCountdown;
    151             mStartTime = now;
    152             float limit = mUserDistance / 20f;
    153             if (mX > limit || mX < -limit || mY > limit || mY < -limit) {
    154                 mX *= GYROSCOPE_RESTORE_FACTOR;
    155                 mY *= GYROSCOPE_RESTORE_FACTOR;
    156                 mZ = (float) -Math.sqrt(
    157                         mUserDistance * mUserDistance - mX * mX - mY * mY);
    158                 mListener.onEyePositionChanged(mX, mY, mZ);
    159             }
    160             return;
    161         }
    162 
    163         float t = (now - mStartTime) / 1000f * mUserDistance * (-mZ);
    164         mStartTime = now;
    165 
    166         float x = -gy, y = -gx;
    167         switch (mDisplay.getRotation()) {
    168             case Surface.ROTATION_90: x = -gx; y= gy; break;
    169             case Surface.ROTATION_180: x = gy; y = gx; break;
    170             case Surface.ROTATION_270: x = gx; y = -gy; break;
    171         }
    172 
    173         mX = Utils.clamp((float) (mX + x * t / Math.hypot(mZ, mX)),
    174                 -mLimit, mLimit) * GYROSCOPE_RESTORE_FACTOR;
    175         mY = Utils.clamp((float) (mY + y * t / Math.hypot(mZ, mY)),
    176                 -mLimit, mLimit) * GYROSCOPE_RESTORE_FACTOR;
    177 
    178         mZ = -FloatMath.sqrt(
    179                 mUserDistance * mUserDistance - mX * mX - mY * mY);
    180         mListener.onEyePositionChanged(mX, mY, mZ);
    181     }
    182 
    183     private class PositionListener implements SensorEventListener {
    184         @Override
    185         public void onAccuracyChanged(Sensor sensor, int accuracy) {
    186         }
    187 
    188         @Override
    189         public void onSensorChanged(SensorEvent event) {
    190             switch (event.sensor.getType()) {
    191                 case Sensor.TYPE_GYROSCOPE: {
    192                     onGyroscopeChanged(
    193                             event.values[0], event.values[1], event.values[2]);
    194                     break;
    195                 }
    196                 case Sensor.TYPE_ACCELEROMETER: {
    197                     onAccelerometerChanged(
    198                             event.values[0], event.values[1], event.values[2]);
    199                 }
    200             }
    201         }
    202     }
    203 
    204     public void pause() {
    205         if (mSensor != null) {
    206             SensorManager sManager = (SensorManager) mContext
    207                     .getSystemService(Context.SENSOR_SERVICE);
    208             sManager.unregisterListener(mPositionListener);
    209         }
    210     }
    211 
    212     public void resume() {
    213         if (mSensor != null) {
    214             SensorManager sManager = (SensorManager) mContext
    215                     .getSystemService(Context.SENSOR_SERVICE);
    216             sManager.registerListener(mPositionListener,
    217                     mSensor, SensorManager.SENSOR_DELAY_GAME);
    218         }
    219 
    220         mStartTime = NOT_STARTED;
    221         mGyroscopeCountdown = GYROSCOPE_SETTLE_DOWN;
    222         mX = mY = 0;
    223         mZ = -mUserDistance;
    224         mListener.onEyePositionChanged(mX, mY, mZ);
    225     }
    226 }
    227