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