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 android.webkit;
18
19import android.content.Context;
20import android.hardware.Sensor;
21import android.hardware.SensorEvent;
22import android.hardware.SensorEventListener;
23import android.hardware.SensorManager;
24import android.os.Handler;
25import android.webkit.DeviceMotionAndOrientationManager;
26import java.lang.Runnable;
27import java.util.List;
28
29
30final class DeviceOrientationService implements SensorEventListener {
31    // The gravity vector expressed in the body frame.
32    private float[] mGravityVector;
33    // The geomagnetic vector expressed in the body frame.
34    private float[] mMagneticFieldVector;
35
36    private DeviceMotionAndOrientationManager mManager;
37    private boolean mIsRunning;
38    private Handler mHandler;
39    private SensorManager mSensorManager;
40    private Context mContext;
41    private Double mAlpha;
42    private Double mBeta;
43    private Double mGamma;
44    private boolean mHaveSentErrorEvent;
45
46    private static final double DELTA_DEGRESS = 1.0;
47
48    public DeviceOrientationService(DeviceMotionAndOrientationManager manager, Context context) {
49        mManager = manager;
50        assert(mManager != null);
51        mContext = context;
52        assert(mContext != null);
53     }
54
55    public void start() {
56        mIsRunning = true;
57        registerForSensors();
58    }
59
60    public void stop() {
61        mIsRunning = false;
62        unregisterFromSensors();
63    }
64
65    public void suspend() {
66        if (mIsRunning) {
67            unregisterFromSensors();
68        }
69    }
70
71    public void resume() {
72        if (mIsRunning) {
73            registerForSensors();
74        }
75    }
76
77    private void sendErrorEvent() {
78        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
79        // The spec requires that each listener receives the error event only once.
80        if (mHaveSentErrorEvent)
81            return;
82        mHaveSentErrorEvent = true;
83        mHandler.post(new Runnable() {
84            @Override
85            public void run() {
86                assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
87                if (mIsRunning) {
88                    // The special case of all nulls is used to signify a failure to get data.
89                    mManager.onOrientationChange(null, null, null);
90                }
91            }
92        });
93    }
94
95    private void registerForSensors() {
96        if (mHandler == null) {
97            mHandler = new Handler();
98        }
99        if (!registerForAccelerometerSensor() || !registerForMagneticFieldSensor()) {
100            unregisterFromSensors();
101            sendErrorEvent();
102        }
103    }
104
105    private void getOrientationUsingGetRotationMatrix() {
106        if (mGravityVector == null || mMagneticFieldVector == null) {
107            return;
108        }
109
110        // Get the rotation matrix.
111        // The rotation matrix that transforms from the body frame to the earth frame.
112        float[] deviceRotationMatrix = new float[9];
113        if (!SensorManager.getRotationMatrix(
114                deviceRotationMatrix, null, mGravityVector, mMagneticFieldVector)) {
115            return;
116        }
117
118        // Convert rotation matrix to rotation angles.
119        // Assuming that the rotations are appied in the order listed at
120        // http://developer.android.com/reference/android/hardware/SensorEvent.html#values
121        // the rotations are applied about the same axes and in the same order as required by the
122        // API. The only conversions are sign changes as follows.
123        // The angles are in radians
124        float[] rotationAngles = new float[3];
125        SensorManager.getOrientation(deviceRotationMatrix, rotationAngles);
126        double alpha = Math.toDegrees(-rotationAngles[0]);
127        while (alpha < 0.0) { alpha += 360.0; } // [0, 360)
128        double beta = Math.toDegrees(-rotationAngles[1]);
129        while (beta < -180.0) { beta += 360.0; } // [-180, 180)
130        double gamma = Math.toDegrees(rotationAngles[2]);
131        while (gamma < -90.0) { gamma += 360.0; } // [-90, 90)
132
133        maybeSendChange(alpha, beta, gamma);
134    }
135
136    private SensorManager getSensorManager() {
137        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
138        if (mSensorManager == null) {
139            mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
140        }
141        return mSensorManager;
142    }
143
144    private boolean registerForAccelerometerSensor() {
145        List<Sensor> sensors = getSensorManager().getSensorList(Sensor.TYPE_ACCELEROMETER);
146        if (sensors.isEmpty()) {
147            return false;
148        }
149        // TODO: Consider handling multiple sensors.
150        return getSensorManager().registerListener(
151                this, sensors.get(0), SensorManager.SENSOR_DELAY_FASTEST, mHandler);
152    }
153
154    private boolean registerForMagneticFieldSensor() {
155        List<Sensor> sensors = getSensorManager().getSensorList(Sensor.TYPE_MAGNETIC_FIELD);
156        if (sensors.isEmpty()) {
157            return false;
158        }
159        // TODO: Consider handling multiple sensors.
160        return getSensorManager().registerListener(
161                this, sensors.get(0), SensorManager.SENSOR_DELAY_FASTEST, mHandler);
162    }
163
164    private void unregisterFromSensors() {
165        getSensorManager().unregisterListener(this);
166    }
167
168    private void maybeSendChange(double alpha, double beta, double gamma) {
169        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
170        if (mAlpha == null || mBeta == null || mGamma == null
171                || Math.abs(alpha - mAlpha) > DELTA_DEGRESS
172                || Math.abs(beta - mBeta) > DELTA_DEGRESS
173                || Math.abs(gamma - mGamma) > DELTA_DEGRESS) {
174            mAlpha = alpha;
175            mBeta = beta;
176            mGamma = gamma;
177            mManager.onOrientationChange(mAlpha, mBeta, mGamma);
178            // Now that we have successfully sent some data, reset whether we've sent an error.
179            mHaveSentErrorEvent = false;
180        }
181    }
182
183    /**
184     * SensorEventListener implementation.
185     * Callbacks happen on the thread on which we registered - the WebCore thread.
186     */
187    @Override
188    public void onSensorChanged(SensorEvent event) {
189        assert(event.values.length == 3);
190        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
191
192        // We may get callbacks after the call to getSensorManager().unregisterListener() returns.
193        if (!mIsRunning) {
194            return;
195        }
196
197        switch (event.sensor.getType()) {
198          case Sensor.TYPE_ACCELEROMETER:
199            if (mGravityVector == null) {
200                mGravityVector = new float[3];
201            }
202            mGravityVector[0] = event.values[0];
203            mGravityVector[1] = event.values[1];
204            mGravityVector[2] = event.values[2];
205            getOrientationUsingGetRotationMatrix();
206            break;
207          case Sensor.TYPE_MAGNETIC_FIELD:
208            if (mMagneticFieldVector == null) {
209                mMagneticFieldVector = new float[3];
210            }
211            mMagneticFieldVector[0] = event.values[0];
212            mMagneticFieldVector[1] = event.values[1];
213            mMagneticFieldVector[2] = event.values[2];
214            getOrientationUsingGetRotationMatrix();
215            break;
216          default:
217            assert(false);
218        }
219    }
220
221    @Override
222    public void onAccuracyChanged(Sensor sensor, int accuracy) {
223        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
224    }
225}
226