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