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 
17 package com.android.camera.app;
18 
19 import android.app.Activity;
20 import android.content.ContentResolver;
21 import android.content.Context;
22 import android.content.pm.ActivityInfo;
23 import android.content.res.Configuration;
24 import android.os.Handler;
25 import android.provider.Settings;
26 import android.view.OrientationEventListener;
27 import android.view.Surface;
28 
29 import com.android.camera.debug.Log;
30 import com.android.camera.util.ApiHelper;
31 
32 import java.util.ArrayList;
33 import 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  */
40 public 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 
OrientationChangeCallback(Handler handler, OnOrientationChangeListener listener)62         OrientationChangeCallback(Handler handler, OnOrientationChangeListener listener) {
63             mHandler = handler;
64             mListener = listener;
65         }
66 
postOrientationChangeCallback(final int orientation)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
equals(Object o)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 
OrientationManagerImpl(Activity activity)89     public OrientationManagerImpl(Activity activity) {
90         mActivity = activity;
91         mOrientationListener = new MyOrientationEventListener(activity);
92     }
93 
resume()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 
pause()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
addOnOrientationChangeListener(Handler handler, OnOrientationChangeListener listener)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
removeOnOrientationChangeListener(Handler handler, OnOrientationChangeListener listener)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
lockOrientation()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
unlockOrientation()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
isOrientationLocked()157     public boolean isOrientationLocked() {
158         return (mOrientationLocked || mRotationLockedSetting);
159     }
160 
calculateCurrentScreenOrientation()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             // TODO: We have two copies of the rounding method: one is CameraUtil.roundOrientation
199             // and the other is OrientationManagerImpl.roundOrientation. The same computation is
200             // done twice when orientation is changed. We should remove the duplicate. b/17440795
201             final int roundedOrientation = roundOrientation(orientation, 0);
202             for (OrientationChangeCallback l : mListeners) {
203                 l.postOrientationChangeCallback(roundedOrientation);
204             }
205         }
206     }
207 
208     @Override
209     public int getDisplayRotation() {
210         return getDisplayRotation(mActivity);
211     }
212 
213     private static int roundOrientation(int orientation, int orientationHistory) {
214         boolean changeOrientation = false;
215         if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {
216             changeOrientation = true;
217         } else {
218             int dist = Math.abs(orientation - orientationHistory);
219             dist = Math.min(dist, 360 - dist);
220             changeOrientation = (dist >= 45 + ORIENTATION_HYSTERESIS);
221         }
222         if (changeOrientation) {
223             return ((orientation + 45) / 90 * 90) % 360;
224         }
225         return orientationHistory;
226     }
227 
getDisplayRotation(Activity activity)228     private static int getDisplayRotation(Activity activity) {
229         int rotation = activity.getWindowManager().getDefaultDisplay()
230                 .getRotation();
231         switch (rotation) {
232             case Surface.ROTATION_0: return 0;
233             case Surface.ROTATION_90: return 90;
234             case Surface.ROTATION_180: return 180;
235             case Surface.ROTATION_270: return 270;
236         }
237         return 0;
238     }
239 }
240