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.ui; 18 19 import android.app.Activity; 20 import android.content.Context; 21 import android.content.res.Configuration; 22 import android.util.AttributeSet; 23 import android.view.Gravity; 24 import android.view.View; 25 import android.view.ViewGroup; 26 import android.widget.FrameLayout; 27 28 import 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 38 public 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 }