OrientationManagerImpl.java revision 9dc4262cea73123b2a73cc0b0f762bb9ce2d5622
1/*
2 * Copyright (C) 2013 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.camera.app;
18
19import android.app.Activity;
20import android.content.ContentResolver;
21import android.content.Context;
22import android.content.pm.ActivityInfo;
23import android.content.res.Configuration;
24import android.os.Handler;
25import android.provider.Settings;
26import android.view.OrientationEventListener;
27import android.view.Surface;
28
29import com.android.camera.debug.Log;
30import com.android.camera.util.ApiHelper;
31
32import java.util.ArrayList;
33import java.util.List;
34
35/**
36 * The implementation of {@link com.android.camera.app.OrientationManager}
37 * by {@link android.view.OrientationEventListener}.
38 * TODO: make this class package-private
39 */
40public class OrientationManagerImpl implements OrientationManager {
41    private static final Log.Tag TAG = new Log.Tag("OrientMgrImpl");
42
43    // Orientation hysteresis amount used in rounding, in degrees
44    private static final int ORIENTATION_HYSTERESIS = 5;
45
46    private final Activity mActivity;
47    private final MyOrientationEventListener mOrientationListener;
48    // If the framework orientation is locked.
49    private boolean mOrientationLocked = false;
50
51    // This is true if "Settings -> Display -> Rotation Lock" is checked. We
52    // don't allow the orientation to be unlocked if the value is true.
53    private boolean mRotationLockedSetting = false;
54
55    private final List<OrientationChangeCallback> mListeners =
56            new ArrayList<OrientationChangeCallback>();
57
58    private static class OrientationChangeCallback {
59        private final Handler mHandler;
60        private final OnOrientationChangeListener mListener;
61
62        OrientationChangeCallback(Handler handler, OnOrientationChangeListener listener) {
63            mHandler = handler;
64            mListener = listener;
65        }
66
67        public void postOrientationChangeCallback(final int orientation) {
68            mHandler.post(new Runnable() {
69                @Override
70                public void run() {
71                    mListener.onOrientationChanged(orientation);
72                }
73            });
74        }
75
76        @Override
77        public boolean equals(Object o) {
78            if (o != null && o instanceof OrientationChangeCallback) {
79                OrientationChangeCallback c = (OrientationChangeCallback) o;
80                if (mHandler == c.mHandler && mListener == c.mListener) {
81                    return true;
82                }
83                return false;
84            }
85            return false;
86        }
87    }
88
89    public OrientationManagerImpl(Activity activity) {
90        mActivity = activity;
91        mOrientationListener = new MyOrientationEventListener(activity);
92    }
93
94    public void resume() {
95        ContentResolver resolver = mActivity.getContentResolver();
96        mRotationLockedSetting = Settings.System.getInt(
97                resolver, Settings.System.ACCELEROMETER_ROTATION, 0) != 1;
98        mOrientationListener.enable();
99    }
100
101    public void pause() {
102        mOrientationListener.disable();
103    }
104
105    ////////////////////////////////////////////////////////////////////////////
106    //  Orientation handling
107    //
108    //  We can choose to lock the framework orientation or not. If we lock the
109    //  framework orientation, we calculate a a compensation value according to
110    //  current device orientation and send it to listeners. If we don't lock
111    //  the framework orientation, we always set the compensation value to 0.
112    ////////////////////////////////////////////////////////////////////////////
113
114    @Override
115    public void addOnOrientationChangeListener(Handler handler,
116            OnOrientationChangeListener listener) {
117        OrientationChangeCallback callback = new OrientationChangeCallback(handler, listener);
118        if (mListeners.contains(callback)) {
119            return;
120        }
121        mListeners.add(callback);
122    }
123
124    @Override
125    public void removeOnOrientationChangeListener(Handler handler,
126            OnOrientationChangeListener listener) {
127        OrientationChangeCallback callback = new OrientationChangeCallback(handler, listener);
128        if (!mListeners.remove(callback)) {
129            Log.v(TAG, "Removing non-existing listener.");
130        }
131    }
132
133    @Override
134    public void lockOrientation() {
135        if (mOrientationLocked || mRotationLockedSetting) {
136            return;
137        }
138        mOrientationLocked = true;
139        if (ApiHelper.HAS_ORIENTATION_LOCK) {
140            mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
141        } else {
142            mActivity.setRequestedOrientation(calculateCurrentScreenOrientation());
143        }
144    }
145
146    @Override
147    public void unlockOrientation() {
148        if (!mOrientationLocked || mRotationLockedSetting) {
149            return;
150        }
151        mOrientationLocked = false;
152        Log.d(TAG, "unlock orientation");
153        mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
154    }
155
156    @Override
157    public boolean isOrientationLocked() {
158        return (mOrientationLocked || mRotationLockedSetting);
159    }
160
161    private int calculateCurrentScreenOrientation() {
162        int displayRotation = getDisplayRotation();
163        // Display rotation >= 180 means we need to use the REVERSE landscape/portrait
164        boolean standard = displayRotation < 180;
165        if (mActivity.getResources().getConfiguration().orientation
166                == Configuration.ORIENTATION_LANDSCAPE) {
167            return standard
168                    ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
169                    : ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
170        } else {
171            if (displayRotation == 90 || displayRotation == 270) {
172                // If displayRotation = 90 or 270 then we are on a landscape
173                // device. On landscape devices, portrait is a 90 degree
174                // clockwise rotation from landscape, so we need
175                // to flip which portrait we pick as display rotation is counter clockwise
176                standard = !standard;
177            }
178            return standard
179                    ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
180                    : ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
181        }
182    }
183
184    // This listens to the device orientation, so we can update the compensation.
185    private class MyOrientationEventListener extends OrientationEventListener {
186        public MyOrientationEventListener(Context context) {
187            super(context);
188        }
189
190        @Override
191        public void onOrientationChanged(int orientation) {
192            // We keep the last known orientation. So if the user first orient
193            // the camera then point the camera to floor or sky, we still have
194            // the correct orientation.
195            if (orientation == ORIENTATION_UNKNOWN) {
196                return;
197            }
198            final int roundedOrientation = roundOrientation(orientation, 0);
199
200            for (OrientationChangeCallback l : mListeners) {
201                l.postOrientationChangeCallback(roundedOrientation);
202            }
203        }
204    }
205
206    @Override
207    public int getDisplayRotation() {
208        return getDisplayRotation(mActivity);
209    }
210
211    private static int roundOrientation(int orientation, int orientationHistory) {
212        boolean changeOrientation = false;
213        if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {
214            changeOrientation = true;
215        } else {
216            int dist = Math.abs(orientation - orientationHistory);
217            dist = Math.min(dist, 360 - dist);
218            changeOrientation = (dist >= 45 + ORIENTATION_HYSTERESIS);
219        }
220        if (changeOrientation) {
221            return ((orientation + 45) / 90 * 90) % 360;
222        }
223        return orientationHistory;
224    }
225
226    private static int getDisplayRotation(Activity activity) {
227        int rotation = activity.getWindowManager().getDefaultDisplay()
228                .getRotation();
229        switch (rotation) {
230            case Surface.ROTATION_0: return 0;
231            case Surface.ROTATION_90: return 90;
232            case Surface.ROTATION_180: return 180;
233            case Surface.ROTATION_270: return 270;
234        }
235        return 0;
236    }
237}
238