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
17package com.android.gallery3d.app;
18
19import android.content.Context;
20import android.hardware.Sensor;
21import android.hardware.SensorEvent;
22import android.hardware.SensorEventListener;
23import android.hardware.SensorManager;
24import android.os.SystemClock;
25import android.util.FloatMath;
26import android.view.Display;
27import android.view.Surface;
28import android.view.WindowManager;
29
30import com.android.gallery3d.common.Utils;
31import com.android.gallery3d.util.GalleryUtils;
32
33public 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