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.ui;
18
19import android.app.Activity;
20import android.content.Context;
21import android.content.res.Configuration;
22import android.util.AttributeSet;
23import android.view.Gravity;
24import android.view.View;
25import android.view.ViewGroup;
26import android.widget.FrameLayout;
27
28import com.android.camera.util.CameraUtil;
29
30/* RotatableLayout rotates itself as well as all its children when orientation
31 * changes. Specifically, when going from portrait to landscape, camera
32 * controls move from the bottom of the screen to right side of the screen
33 * (i.e. counter clockwise). Similarly, when the screen changes to portrait, we
34 * need to move the controls from right side to the bottom of the screen, which
35 * is a clockwise rotation.
36 */
37
38public class RotatableLayout extends FrameLayout {
39
40    private static final String TAG = "RotatableLayout";
41    private static final int UNKOWN_ORIENTATION = -1;
42    // Initial orientation of the layout (ORIENTATION_PORTRAIT, or ORIENTATION_LANDSCAPE)
43    private int mInitialOrientation;
44    private int mPrevRotation = UNKOWN_ORIENTATION;
45    private boolean mIsDefaultToPortrait = false;
46
47    public RotatableLayout(Context context, AttributeSet attrs, int defStyle) {
48        super(context, attrs, defStyle);
49        init();
50    }
51
52    public RotatableLayout(Context context, AttributeSet attrs) {
53        super(context, attrs);
54        init();
55    }
56
57    public RotatableLayout(Context context) {
58        super(context);
59        init();
60    }
61
62    private void init() {
63        mInitialOrientation = getResources().getConfiguration().orientation;
64    }
65
66    @Override
67    public void onAttachedToWindow() {
68        // Before the first time this view is attached to window, device rotation
69        // will not trigger onConfigurationChanged callback. So in the first run
70        // we need to rotate the view if necessary. After that, onConfigurationChanged
71        // call will track all the subsequent device rotation.
72        if (mPrevRotation == UNKOWN_ORIENTATION) {
73            mIsDefaultToPortrait = CameraUtil.isDefaultToPortrait((Activity) getContext());
74            if (mIsDefaultToPortrait) {
75                // Natural orientation for tablet is landscape
76                mPrevRotation =  mInitialOrientation == Configuration.ORIENTATION_PORTRAIT ?
77                        0 : 90;
78            } else {
79                // When tablet orientation is 0 or 270 (i.e. getUnifiedOrientation
80                // = 0 or 90), we load the layout resource without any rotation.
81                mPrevRotation =  mInitialOrientation == Configuration.ORIENTATION_LANDSCAPE ?
82                        0 : 270;
83            }
84
85            // check if there is any rotation before the view is attached to window
86            rotateIfNeeded();
87        }
88    }
89
90    private void rotateIfNeeded() {
91        if (mPrevRotation == UNKOWN_ORIENTATION) {
92            return;
93        }
94        int rotation = CameraUtil.getDisplayRotation((Activity) getContext());
95        int diff = (rotation - mPrevRotation + 360) % 360;
96        if ( diff == 0) {
97            // No rotation
98            return;
99        } else if (diff == 180) {
100            // 180-degree rotation
101            mPrevRotation = rotation;
102            flipChildren();
103            return;
104        }
105        // 90 or 270-degree rotation
106        boolean clockwise = isClockWiseRotation(mPrevRotation, rotation);
107        mPrevRotation = rotation;
108        rotateLayout(clockwise);
109    }
110
111    protected int getUnifiedRotation() {
112        // all the layout code assumes camera device orientation to be portrait
113        // adjust rotation for landscape
114        int rotation = CameraUtil.getDisplayRotation((Activity) getContext());
115        if (!mIsDefaultToPortrait) {
116            return (rotation + 90) % 360;
117        }
118        return rotation;
119    }
120
121    public void checkLayoutFlip() {
122        int currentRotation = CameraUtil.getDisplayRotation((Activity) getContext());
123        if ((currentRotation - mPrevRotation + 360) % 360 == 180) {
124            mPrevRotation = currentRotation;
125            flipChildren();
126            requestLayout();
127        }
128    }
129
130    @Override
131    public void onWindowVisibilityChanged(int visibility) {
132        if (visibility == View.VISIBLE) {
133            // Make sure when coming back from onPause, the layout is rotated correctly
134            checkLayoutFlip();
135        }
136    }
137
138    @Override
139    public void onConfigurationChanged(Configuration config) {
140        super.onConfigurationChanged(config);
141        rotateIfNeeded();
142    }
143
144    protected void rotateLayout(boolean clockwise) {
145        // Change the size of the layout
146        ViewGroup.LayoutParams lp = getLayoutParams();
147        int width = lp.width;
148        int height = lp.height;
149        lp.height = width;
150        lp.width = height;
151        setLayoutParams(lp);
152
153        // rotate all the children
154        rotateChildren(clockwise);
155    }
156
157    protected void rotateChildren(boolean clockwise) {
158        int childCount = getChildCount();
159        for (int i = 0; i < childCount; i++) {
160            View child = getChildAt(i);
161            rotate(child, clockwise);
162        }
163    }
164
165    protected void flipChildren() {
166        int childCount = getChildCount();
167        for (int i = 0; i < childCount; i++) {
168            View child = getChildAt(i);
169            flip(child);
170        }
171    }
172
173    public static boolean isClockWiseRotation(int prevRotation, int currentRotation) {
174        if (prevRotation == (currentRotation + 90) % 360) {
175            return true;
176        }
177        return false;
178    }
179
180    public static void rotate(View view, boolean isClockwise) {
181        if (isClockwise) {
182            rotateClockwise(view);
183        } else {
184            rotateCounterClockwise(view);
185        }
186    }
187
188    private static boolean contains(int value, int mask) {
189        return (value & mask) == mask;
190    }
191
192    public static void rotateClockwise(View view) {
193        if (view == null) return;
194        LayoutParams lp = (LayoutParams) view.getLayoutParams();
195        int gravity = lp.gravity;
196        int ngravity = 0;
197        // rotate gravity
198        if (contains(gravity, Gravity.LEFT)) {
199            ngravity |= Gravity.TOP;
200        }
201        if (contains(gravity, Gravity.RIGHT)) {
202            ngravity |= Gravity.BOTTOM;
203        }
204        if (contains(gravity, Gravity.TOP)) {
205            ngravity |= Gravity.RIGHT;
206        }
207        if (contains(gravity, Gravity.BOTTOM)) {
208            ngravity |= Gravity.LEFT;
209        }
210        if (contains(gravity, Gravity.CENTER)) {
211            ngravity |= Gravity.CENTER;
212        }
213        if (contains(gravity, Gravity.CENTER_HORIZONTAL)) {
214            ngravity |= Gravity.CENTER_VERTICAL;
215        }
216        if (contains(gravity, Gravity.CENTER_VERTICAL)) {
217            ngravity |= Gravity.CENTER_HORIZONTAL;
218        }
219        lp.gravity = ngravity;
220        int ml = lp.leftMargin;
221        int mr = lp.rightMargin;
222        int mt = lp.topMargin;
223        int mb = lp.bottomMargin;
224        lp.leftMargin = mb;
225        lp.rightMargin = mt;
226        lp.topMargin = ml;
227        lp.bottomMargin = mr;
228        int width = lp.width;
229        int height = lp.height;
230        lp.width = height;
231        lp.height = width;
232        view.setLayoutParams(lp);
233    }
234
235    public static void rotateCounterClockwise(View view) {
236        if (view == null) return;
237        LayoutParams lp = (LayoutParams) view.getLayoutParams();
238        int gravity = lp.gravity;
239        int ngravity = 0;
240        // change gravity
241        if (contains(gravity, Gravity.RIGHT)) {
242            ngravity |= Gravity.TOP;
243        }
244        if (contains(gravity, Gravity.LEFT)) {
245            ngravity |= Gravity.BOTTOM;
246        }
247        if (contains(gravity, Gravity.TOP)) {
248            ngravity |= Gravity.LEFT;
249        }
250        if (contains(gravity, Gravity.BOTTOM)) {
251            ngravity |= Gravity.RIGHT;
252        }
253        if (contains(gravity, Gravity.CENTER)) {
254            ngravity |= Gravity.CENTER;
255        }
256        if (contains(gravity, Gravity.CENTER_HORIZONTAL)) {
257            ngravity |= Gravity.CENTER_VERTICAL;
258        }
259        if (contains(gravity, Gravity.CENTER_VERTICAL)) {
260            ngravity |= Gravity.CENTER_HORIZONTAL;
261        }
262        lp.gravity = ngravity;
263        int ml = lp.leftMargin;
264        int mr = lp.rightMargin;
265        int mt = lp.topMargin;
266        int mb = lp.bottomMargin;
267        lp.leftMargin = mt;
268        lp.rightMargin = mb;
269        lp.topMargin = mr;
270        lp.bottomMargin = ml;
271        int width = lp.width;
272        int height = lp.height;
273        lp.width = height;
274        lp.height = width;
275        view.setLayoutParams(lp);
276    }
277
278    // Rotate a given view 180 degrees
279    public static void flip(View view) {
280        rotateClockwise(view);
281        rotateClockwise(view);
282    }
283}