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.graphics.Point;
25 import android.os.Handler;
26 import android.provider.Settings;
27 import android.view.Display;
28 import android.view.OrientationEventListener;
29 import android.view.Surface;
30 import android.view.WindowManager;
31 
32 import com.android.camera.debug.Log;
33 import com.android.camera.util.AndroidServices;
34 import com.android.camera.util.ApiHelper;
35 import com.android.camera.util.CameraUtil;
36 
37 import java.util.ArrayList;
38 import java.util.List;
39 
40 /**
41  * The implementation of {@link com.android.camera.app.OrientationManager}
42  * by {@link android.view.OrientationEventListener}.
43  */
44 public class OrientationManagerImpl implements OrientationManager {
45     private static final Log.Tag TAG = new Log.Tag("OrientMgrImpl");
46 
47     // DeviceOrientation hysteresis amount used in rounding, in degrees
48     private static final int ORIENTATION_HYSTERESIS = 5;
49 
50     private final Activity mActivity;
51 
52     // The handler used to invoke listener callback.
53     private final Handler mHandler;
54 
55     private final MyOrientationEventListener mOrientationListener;
56 
57     // We keep the last known orientation. So if the user first orient
58     // the camera then point the camera to floor or sky, we still have
59     // the correct orientation.
60     private DeviceOrientation mLastDeviceOrientation = DeviceOrientation.CLOCKWISE_0;
61 
62     // If the framework orientation is locked.
63     private boolean mOrientationLocked = false;
64 
65     // This is true if "Settings -> Display -> Rotation Lock" is checked. We
66     // don't allow the orientation to be unlocked if the value is true.
67     private boolean mRotationLockedSetting = false;
68 
69     private final List<OnOrientationChangeListener> mListeners =
70             new ArrayList<OnOrientationChangeListener>();
71 
72     private final boolean mIsDefaultToPortrait;
73 
74     /**
75      * Instantiates a new orientation manager.
76      *
77      * @param activity The main activity object.
78      * @param handler The handler used to invoke listener callback.
79      */
OrientationManagerImpl(Activity activity, Handler handler)80     public OrientationManagerImpl(Activity activity, Handler handler) {
81         mActivity = activity;
82         mOrientationListener = new MyOrientationEventListener(activity);
83         mHandler = handler;
84         mIsDefaultToPortrait = isDefaultToPortrait(activity);
85     }
86 
resume()87     public void resume() {
88         ContentResolver resolver = mActivity.getContentResolver();
89         mRotationLockedSetting = Settings.System.getInt(
90                 resolver, Settings.System.ACCELEROMETER_ROTATION, 0) != 1;
91         mOrientationListener.enable();
92     }
93 
pause()94     public void pause() {
95         mOrientationListener.disable();
96     }
97 
98     @Override
getDeviceNaturalOrientation()99     public DeviceNaturalOrientation getDeviceNaturalOrientation() {
100         return mIsDefaultToPortrait ? DeviceNaturalOrientation.PORTRAIT :
101                 DeviceNaturalOrientation.LANDSCAPE;
102     }
103 
104     @Override
getDeviceOrientation()105     public DeviceOrientation getDeviceOrientation() {
106         return mLastDeviceOrientation;
107     }
108 
109     @Override
getDisplayRotation()110     public DeviceOrientation getDisplayRotation() {
111         return DeviceOrientation.from((360 - CameraUtil.getDisplayRotation()) % 360);
112     }
113 
114     @Override
addOnOrientationChangeListener(OnOrientationChangeListener listener)115     public void addOnOrientationChangeListener(OnOrientationChangeListener listener) {
116         if (mListeners.contains(listener)) {
117             return;
118         }
119         mListeners.add(listener);
120     }
121 
122     @Override
removeOnOrientationChangeListener(OnOrientationChangeListener listener)123     public void removeOnOrientationChangeListener(OnOrientationChangeListener listener) {
124         if (!mListeners.remove(listener)) {
125             Log.v(TAG, "Removing non-existing listener.");
126         }
127     }
128 
129     @Override
isInLandscape()130     public boolean isInLandscape() {
131         int roundedOrientationDegrees = mLastDeviceOrientation.getDegrees();
132         if (mIsDefaultToPortrait) {
133             if (roundedOrientationDegrees % 180 == 90) {
134                 return true;
135             }
136         } else {
137             if (roundedOrientationDegrees % 180 == 0) {
138                 return true;
139             }
140         }
141         return false;
142     }
143 
144     @Override
isInPortrait()145     public boolean isInPortrait() {
146         return !isInLandscape();
147     }
148 
149     @Override
lockOrientation()150     public void lockOrientation() {
151         if (mOrientationLocked || mRotationLockedSetting) {
152             return;
153         }
154         mOrientationLocked = true;
155         if (ApiHelper.HAS_ORIENTATION_LOCK) {
156             mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
157         } else {
158             mActivity.setRequestedOrientation(calculateCurrentScreenOrientation());
159         }
160     }
161 
162     @Override
unlockOrientation()163     public void unlockOrientation() {
164         if (!mOrientationLocked || mRotationLockedSetting) {
165             return;
166         }
167         mOrientationLocked = false;
168         Log.d(TAG, "unlock orientation");
169         mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
170     }
171 
172     @Override
isOrientationLocked()173     public boolean isOrientationLocked() {
174         return (mOrientationLocked || mRotationLockedSetting);
175     }
176 
calculateCurrentScreenOrientation()177     private int calculateCurrentScreenOrientation() {
178         int displayRotation = getDisplayRotation(mActivity);
179         // Display rotation >= 180 means we need to use the REVERSE landscape/portrait
180         boolean standard = displayRotation < 180;
181         if (mActivity.getResources().getConfiguration().orientation
182                 == Configuration.ORIENTATION_LANDSCAPE) {
183             return standard
184                     ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
185                     : ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
186         } else {
187             if (displayRotation == 90 || displayRotation == 270) {
188                 // If displayRotation = 90 or 270 then we are on a landscape
189                 // device. On landscape devices, portrait is a 90 degree
190                 // clockwise rotation from landscape, so we need
191                 // to flip which portrait we pick as display rotation is counter clockwise
192                 standard = !standard;
193             }
194             return standard
195                     ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
196                     : ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
197         }
198     }
199 
200     // This listens to the device orientation, so we can update the compensation.
201     private class MyOrientationEventListener extends OrientationEventListener {
202         public MyOrientationEventListener(Context context) {
203             super(context);
204         }
205 
206         @Override
207         public void onOrientationChanged(int orientation) {
208             if (orientation == ORIENTATION_UNKNOWN) {
209                 return;
210             }
211 
212             final DeviceOrientation roundedDeviceOrientation =
213                     roundOrientation(mLastDeviceOrientation, orientation);
214             if (roundedDeviceOrientation == mLastDeviceOrientation) {
215                 return;
216             }
217             Log.v(TAG, "orientation changed (from:to) " + mLastDeviceOrientation +
218                     ":" + roundedDeviceOrientation);
219             mLastDeviceOrientation = roundedDeviceOrientation;
220 
221             for (final OnOrientationChangeListener listener : mListeners) {
222                 mHandler.post(new Runnable() {
223                     @Override
224                     public void run() {
225                         listener.onOrientationChanged(OrientationManagerImpl.this, roundedDeviceOrientation);
226                     }
227                 });
228             }
229         }
230     }
231 
232     private static DeviceOrientation roundOrientation(DeviceOrientation oldDeviceOrientation,
233                                                       int newRawOrientation) {
234         int dist = Math.abs(newRawOrientation - oldDeviceOrientation.getDegrees());
235         dist = Math.min(dist, 360 - dist);
236         boolean isOrientationChanged = (dist >= 45 + ORIENTATION_HYSTERESIS);
237 
238         if (isOrientationChanged) {
239             int newRoundedOrientation = ((newRawOrientation + 45) / 90 * 90) % 360;
240             switch (newRoundedOrientation) {
241                 case 0:
242                     return DeviceOrientation.CLOCKWISE_0;
243                 case 90:
244                     return DeviceOrientation.CLOCKWISE_90;
245                 case 180:
246                     return DeviceOrientation.CLOCKWISE_180;
247                 case 270:
248                     return DeviceOrientation.CLOCKWISE_270;
249             }
250         }
251         return oldDeviceOrientation;
252     }
253 
getDisplayRotation(Activity activity)254     private static int getDisplayRotation(Activity activity) {
255         int rotation = activity.getWindowManager().getDefaultDisplay()
256                 .getRotation();
257         switch (rotation) {
258             case Surface.ROTATION_0: return 0;
259             case Surface.ROTATION_90: return 90;
260             case Surface.ROTATION_180: return 180;
261             case Surface.ROTATION_270: return 270;
262         }
263         return 0;
264     }
265 
266     /**
267      * Calculate the default orientation of the device based on the width and
268      * height of the display when rotation = 0 (i.e. natural width and height)
269      *
270      * @param context current context
271      * @return whether the default orientation of the device is portrait
272      */
isDefaultToPortrait(Context context)273     private static boolean isDefaultToPortrait(Context context) {
274         Display currentDisplay = AndroidServices.instance().provideWindowManager()
275                 .getDefaultDisplay();
276         Point displaySize = new Point();
277         currentDisplay.getSize(displaySize);
278         int orientation = currentDisplay.getRotation();
279         int naturalWidth, naturalHeight;
280         if (orientation == Surface.ROTATION_0 || orientation == Surface.ROTATION_180) {
281             naturalWidth = displaySize.x;
282             naturalHeight = displaySize.y;
283         } else {
284             naturalWidth = displaySize.y;
285             naturalHeight = displaySize.x;
286         }
287         return naturalWidth < naturalHeight;
288     }
289 }
290