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.graphics.Point; 25import android.os.Handler; 26import android.provider.Settings; 27import android.view.Display; 28import android.view.OrientationEventListener; 29import android.view.Surface; 30import android.view.WindowManager; 31 32import com.android.camera.debug.Log; 33import com.android.camera.util.AndroidServices; 34import com.android.camera.util.ApiHelper; 35import com.android.camera.util.CameraUtil; 36 37import java.util.ArrayList; 38import java.util.List; 39 40/** 41 * The implementation of {@link com.android.camera.app.OrientationManager} 42 * by {@link android.view.OrientationEventListener}. 43 */ 44public 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 */ 80 public OrientationManagerImpl(Activity activity, Handler handler) { 81 mActivity = activity; 82 mOrientationListener = new MyOrientationEventListener(activity); 83 mHandler = handler; 84 mIsDefaultToPortrait = isDefaultToPortrait(activity); 85 } 86 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 94 public void pause() { 95 mOrientationListener.disable(); 96 } 97 98 @Override 99 public DeviceNaturalOrientation getDeviceNaturalOrientation() { 100 return mIsDefaultToPortrait ? DeviceNaturalOrientation.PORTRAIT : 101 DeviceNaturalOrientation.LANDSCAPE; 102 } 103 104 @Override 105 public DeviceOrientation getDeviceOrientation() { 106 return mLastDeviceOrientation; 107 } 108 109 @Override 110 public DeviceOrientation getDisplayRotation() { 111 return DeviceOrientation.from((360 - CameraUtil.getDisplayRotation()) % 360); 112 } 113 114 @Override 115 public void addOnOrientationChangeListener(OnOrientationChangeListener listener) { 116 if (mListeners.contains(listener)) { 117 return; 118 } 119 mListeners.add(listener); 120 } 121 122 @Override 123 public void removeOnOrientationChangeListener(OnOrientationChangeListener listener) { 124 if (!mListeners.remove(listener)) { 125 Log.v(TAG, "Removing non-existing listener."); 126 } 127 } 128 129 @Override 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 145 public boolean isInPortrait() { 146 return !isInLandscape(); 147 } 148 149 @Override 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 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 173 public boolean isOrientationLocked() { 174 return (mOrientationLocked || mRotationLockedSetting); 175 } 176 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 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 */ 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