OrientationManagerImpl.java revision 846d3abfe3da2fa2a5593c7d40a196005408bed1
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.util.Log;
27import android.view.OrientationEventListener;
28import android.view.Surface;
29
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 String TAG = "OrientationManagerImpl";
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    private int calculateCurrentScreenOrientation() {
157        int displayRotation = getDisplayRotation();
158        // Display rotation >= 180 means we need to use the REVERSE landscape/portrait
159        boolean standard = displayRotation < 180;
160        if (mActivity.getResources().getConfiguration().orientation
161                == Configuration.ORIENTATION_LANDSCAPE) {
162            return standard
163                    ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
164                    : ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
165        } else {
166            if (displayRotation == 90 || displayRotation == 270) {
167                // If displayRotation = 90 or 270 then we are on a landscape
168                // device. On landscape devices, portrait is a 90 degree
169                // clockwise rotation from landscape, so we need
170                // to flip which portrait we pick as display rotation is counter clockwise
171                standard = !standard;
172            }
173            return standard
174                    ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
175                    : ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
176        }
177    }
178
179    // This listens to the device orientation, so we can update the compensation.
180    private class MyOrientationEventListener extends OrientationEventListener {
181        public MyOrientationEventListener(Context context) {
182            super(context);
183        }
184
185        @Override
186        public void onOrientationChanged(int orientation) {
187            // We keep the last known orientation. So if the user first orient
188            // the camera then point the camera to floor or sky, we still have
189            // the correct orientation.
190            if (orientation == ORIENTATION_UNKNOWN) {
191                return;
192            }
193            final int roundedOrientation = roundOrientation(orientation, 0);
194
195            for (OrientationChangeCallback l : mListeners) {
196                l.postOrientationChangeCallback(roundedOrientation);
197            }
198        }
199    }
200
201    @Override
202    public int getDisplayRotation() {
203        return getDisplayRotation(mActivity);
204    }
205
206    private static int roundOrientation(int orientation, int orientationHistory) {
207        boolean changeOrientation = false;
208        if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {
209            changeOrientation = true;
210        } else {
211            int dist = Math.abs(orientation - orientationHistory);
212            dist = Math.min(dist, 360 - dist);
213            changeOrientation = (dist >= 45 + ORIENTATION_HYSTERESIS);
214        }
215        if (changeOrientation) {
216            return ((orientation + 45) / 90 * 90) % 360;
217        }
218        return orientationHistory;
219    }
220
221    private static int getDisplayRotation(Activity activity) {
222        int rotation = activity.getWindowManager().getDefaultDisplay()
223                .getRotation();
224        switch (rotation) {
225            case Surface.ROTATION_0: return 0;
226            case Surface.ROTATION_90: return 90;
227            case Surface.ROTATION_180: return 180;
228            case Surface.ROTATION_270: return 270;
229        }
230        return 0;
231    }
232}
233