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