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