OrientationManager.java revision ad08811a71e246d45ecdf97402f08cf7bd68e83b
1ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang/*
2ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang * Copyright (C) 2012 The Android Open Source Project
3ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang *
4ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang * Licensed under the Apache License, Version 2.0 (the "License");
5ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang * you may not use this file except in compliance with the License.
6ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang * You may obtain a copy of the License at
7ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang *
8ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang *      http://www.apache.org/licenses/LICENSE-2.0
9ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang *
10ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang * Unless required by applicable law or agreed to in writing, software
11ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang * distributed under the License is distributed on an "AS IS" BASIS,
12ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang * See the License for the specific language governing permissions and
14ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang * limitations under the License.
15ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang */
16ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
17ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Changpackage com.android.gallery3d.app;
18ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
19ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Changimport android.app.Activity;
20ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Changimport android.content.Context;
21ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Changimport android.content.pm.ActivityInfo;
22ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Changimport android.content.res.Configuration;
23ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Changimport android.view.OrientationEventListener;
24ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Changimport android.view.Surface;
25ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Changimport android.view.ViewConfiguration;
26ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
27ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Changimport java.util.ArrayList;
28ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
29ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Changpublic class OrientationManager {
30ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    private static final String TAG = "OrientationManager";
31ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
32ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    public interface Listener {
33ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        public void onOrientationCompensationChanged(int degrees);
34ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    }
35ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
36ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    // Orientation hysteresis amount used in rounding, in degrees
37ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    private static final int ORIENTATION_HYSTERESIS = 5;
38ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
39ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    private Activity mActivity;
40ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    private ArrayList<Listener> mListeners;
41ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    private MyOrientationEventListener mOrientationListener;
42ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    // The degrees of the device rotated clockwise from its natural orientation.
43ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
44ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    // If the framework orientation is locked.
45ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    private boolean mOrientationLocked = false;
46ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    // The orientation compensation: if the framwork orientation is locked, the
47ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    // device orientation and the framework orientation may be different, so we
48ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    // need to rotate the UI. For example, if this value is 90, the UI
49ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    // components should be rotated 90 degrees counter-clockwise.
50ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    private int mOrientationCompensation = 0;
51ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
52ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    public OrientationManager(Activity activity) {
53ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        mActivity = activity;
54ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        mListeners = new ArrayList<Listener>();
55ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        mOrientationListener = new MyOrientationEventListener(activity);
56ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    }
57ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
58ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    public void resume() {
59ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        mOrientationListener.enable();
60ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    }
61ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
62ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    public void pause() {
63ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        mOrientationListener.disable();
64ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    }
65ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
66ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    public void addListener(Listener listener) {
67ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        synchronized (mListeners) {
68ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            mListeners.add(listener);
69ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        }
70ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    }
71ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
72ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    public void removeListener(Listener listener) {
73ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        synchronized (mListeners) {
74ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            mListeners.remove(listener);
75ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        }
76ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    }
77ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
78ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
79ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    //  Orientation handling
80ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    //
81ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    //  We can choose to lock the framework orientation or not. If we lock the
82ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    //  framework orientation, we calculate a a compensation value according to
83ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    //  current device orientation and send it to listeners. If we don't lock
84ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    //  the framework orientation, we always set the compensation value to 0.
85ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    ////////////////////////////////////////////////////////////////////////////
86ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
87ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    // Lock the framework orientation to the current device orientation
88ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    public void lockOrientation() {
89ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        if (mOrientationLocked) return;
90ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        mOrientationLocked = true;
91ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        if (mActivity.getResources().getConfiguration().orientation
92ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang                == Configuration.ORIENTATION_LANDSCAPE) {
93ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            Log.d(TAG, "lock orientation to landscape");
94ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
95ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        } else {
96ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            Log.d(TAG, "lock orientation to portrait");
97ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
98ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        }
99ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        updateCompensation();
100ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    }
101ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
102ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    // Unlock the framework orientation, so it can change when the device
103ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    // rotates.
104ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    public void unlockOrientation() {
105ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        if (!mOrientationLocked) return;
106ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        mOrientationLocked = false;
107ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        Log.d(TAG, "unlock orientation");
108ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
109ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        disableCompensation();
110ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    }
111ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
112ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    // Calculate the compensation value and send it to listeners.
113ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    private void updateCompensation() {
114ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        if (mOrientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
115ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            return;
116ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        }
117ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
118ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        int orientationCompensation =
119ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            (mOrientation + getDisplayRotation(mActivity)) % 360;
120ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
121ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        if (mOrientationCompensation != orientationCompensation) {
122ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            mOrientationCompensation = orientationCompensation;
123ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            notifyListeners();
124ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        }
125ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    }
126ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
127ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    // Make the compensation value 0 and send it to listeners.
128ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    private void disableCompensation() {
129ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        if (mOrientationCompensation != 0) {
130ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            mOrientationCompensation = 0;
131ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            notifyListeners();
132ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        }
133ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    }
134ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
135ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    private void notifyListeners() {
136ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        synchronized (mListeners) {
137ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            for (int i = 0, n = mListeners.size(); i < n; i++) {
138ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang                mListeners.get(i).onOrientationCompensationChanged(
139ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang                        mOrientationCompensation);
140ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            }
141ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        }
142ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    }
143ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
144ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    // This listens to the device orientation, so we can update the compensation.
145ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    private class MyOrientationEventListener extends OrientationEventListener {
146ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        public MyOrientationEventListener(Context context) {
147ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            super(context);
148ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        }
149ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
150ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        @Override
151ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        public void onOrientationChanged(int orientation) {
152ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            // We keep the last known orientation. So if the user first orient
153ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            // the camera then point the camera to floor or sky, we still have
154ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            // the correct orientation.
155ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            if (orientation == ORIENTATION_UNKNOWN) return;
156ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            mOrientation = roundOrientation(orientation, mOrientation);
157ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            // If the framework orientation is locked, we update the
158ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            // compensation value and notify the listeners.
159ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            if (mOrientationLocked) updateCompensation();
160ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        }
161ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    }
162ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
163ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    public int getDisplayRotation() {
164ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        return getDisplayRotation(mActivity);
165ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    }
166ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
167ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    private static int roundOrientation(int orientation, int orientationHistory) {
168ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        boolean changeOrientation = false;
169ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {
170ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            changeOrientation = true;
171ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        } else {
172ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            int dist = Math.abs(orientation - orientationHistory);
173ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            dist = Math.min(dist, 360 - dist);
174ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            changeOrientation = (dist >= 45 + ORIENTATION_HYSTERESIS);
175ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        }
176ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        if (changeOrientation) {
177ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            return ((orientation + 45) / 90 * 90) % 360;
178ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        }
179ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        return orientationHistory;
180ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    }
181ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang
182ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    private static int getDisplayRotation(Activity activity) {
183ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        int rotation = activity.getWindowManager().getDefaultDisplay()
184ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang                .getRotation();
185ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        switch (rotation) {
186ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            case Surface.ROTATION_0: return 0;
187ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            case Surface.ROTATION_90: return 90;
188ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            case Surface.ROTATION_180: return 180;
189ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang            case Surface.ROTATION_270: return 270;
190ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        }
191ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang        return 0;
192ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang    }
193ad08811a71e246d45ecdf97402f08cf7bd68e83bChih-Chung Chang}
194