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.provider.Settings;
25import android.util.Log;
26import android.view.OrientationEventListener;
27import android.view.Surface;
28
29import com.android.camera.util.ApiHelper;
30
31public class OrientationManager {
32    private static final String TAG = "CAM_OrientationManager";
33
34    // Orientation hysteresis amount used in rounding, in degrees
35    private static final int ORIENTATION_HYSTERESIS = 5;
36
37    private Activity mActivity;
38    private MyOrientationEventListener mOrientationListener;
39    // If the framework orientation is locked.
40    private boolean mOrientationLocked = false;
41
42    // This is true if "Settings -> Display -> Rotation Lock" is checked. We
43    // don't allow the orientation to be unlocked if the value is true.
44    private boolean mRotationLockedSetting = false;
45
46    public OrientationManager(Activity activity) {
47        mActivity = activity;
48        mOrientationListener = new MyOrientationEventListener(activity);
49    }
50
51    public void resume() {
52        ContentResolver resolver = mActivity.getContentResolver();
53        mRotationLockedSetting = Settings.System.getInt(
54                resolver, Settings.System.ACCELEROMETER_ROTATION, 0) != 1;
55        mOrientationListener.enable();
56    }
57
58    public void pause() {
59        mOrientationListener.disable();
60    }
61
62    ////////////////////////////////////////////////////////////////////////////
63    //  Orientation handling
64    //
65    //  We can choose to lock the framework orientation or not. If we lock the
66    //  framework orientation, we calculate a a compensation value according to
67    //  current device orientation and send it to listeners. If we don't lock
68    //  the framework orientation, we always set the compensation value to 0.
69    ////////////////////////////////////////////////////////////////////////////
70
71    /**
72     * Lock the framework orientation to the current device orientation
73     * rotates. No effect if the system setting of auto-rotation is off.
74     */
75    public void lockOrientation() {
76        if (mOrientationLocked || mRotationLockedSetting) return;
77        mOrientationLocked = true;
78        if (ApiHelper.HAS_ORIENTATION_LOCK) {
79            mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
80        } else {
81            mActivity.setRequestedOrientation(calculateCurrentScreenOrientation());
82        }
83    }
84
85    /**
86     * Unlock the framework orientation, so it can change when the device
87     * rotates. No effect if the system setting of auto-rotation is off.
88     */
89    public void unlockOrientation() {
90        if (!mOrientationLocked || mRotationLockedSetting) return;
91        mOrientationLocked = false;
92        Log.d(TAG, "unlock orientation");
93        mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
94    }
95
96    private int calculateCurrentScreenOrientation() {
97        int displayRotation = getDisplayRotation();
98        // Display rotation >= 180 means we need to use the REVERSE landscape/portrait
99        boolean standard = displayRotation < 180;
100        if (mActivity.getResources().getConfiguration().orientation
101                == Configuration.ORIENTATION_LANDSCAPE) {
102            return standard
103                    ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
104                    : ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
105        } else {
106            if (displayRotation == 90 || displayRotation == 270) {
107                // If displayRotation = 90 or 270 then we are on a landscape
108                // device. On landscape devices, portrait is a 90 degree
109                // clockwise rotation from landscape, so we need
110                // to flip which portrait we pick as display rotation is counter clockwise
111                standard = !standard;
112            }
113            return standard
114                    ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
115                    : ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
116        }
117    }
118
119    // This listens to the device orientation, so we can update the compensation.
120    private class MyOrientationEventListener extends OrientationEventListener {
121        public MyOrientationEventListener(Context context) {
122            super(context);
123        }
124
125        @Override
126        public void onOrientationChanged(int orientation) {
127            // We keep the last known orientation. So if the user first orient
128            // the camera then point the camera to floor or sky, we still have
129            // the correct orientation.
130            if (orientation == ORIENTATION_UNKNOWN) return;
131            orientation = roundOrientation(orientation, 0);
132        }
133    }
134
135    public int getDisplayRotation() {
136        return getDisplayRotation(mActivity);
137    }
138
139    public int getCompensation() {
140        return 0;
141    }
142
143    private static int roundOrientation(int orientation, int orientationHistory) {
144        boolean changeOrientation = false;
145        if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {
146            changeOrientation = true;
147        } else {
148            int dist = Math.abs(orientation - orientationHistory);
149            dist = Math.min(dist, 360 - dist);
150            changeOrientation = (dist >= 45 + ORIENTATION_HYSTERESIS);
151        }
152        if (changeOrientation) {
153            return ((orientation + 45) / 90 * 90) % 360;
154        }
155        return orientationHistory;
156    }
157
158    private static int getDisplayRotation(Activity activity) {
159        int rotation = activity.getWindowManager().getDefaultDisplay()
160                .getRotation();
161        switch (rotation) {
162            case Surface.ROTATION_0: return 0;
163            case Surface.ROTATION_90: return 90;
164            case Surface.ROTATION_180: return 180;
165            case Surface.ROTATION_270: return 270;
166        }
167        return 0;
168    }
169}
170