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.os.Handler; 25 import android.provider.Settings; 26 import android.view.OrientationEventListener; 27 import android.view.Surface; 28 29 import com.android.camera.debug.Log; 30 import com.android.camera.util.ApiHelper; 31 32 import java.util.ArrayList; 33 import java.util.List; 34 35 /** 36 * The implementation of {@link com.android.camera.app.OrientationManager} 37 * by {@link android.view.OrientationEventListener}. 38 * TODO: make this class package-private 39 */ 40 public class OrientationManagerImpl implements OrientationManager { 41 private static final Log.Tag TAG = new Log.Tag("OrientMgrImpl"); 42 43 // Orientation hysteresis amount used in rounding, in degrees 44 private static final int ORIENTATION_HYSTERESIS = 5; 45 46 private final Activity mActivity; 47 private final MyOrientationEventListener mOrientationListener; 48 // If the framework orientation is locked. 49 private boolean mOrientationLocked = false; 50 51 // This is true if "Settings -> Display -> Rotation Lock" is checked. We 52 // don't allow the orientation to be unlocked if the value is true. 53 private boolean mRotationLockedSetting = false; 54 55 private final List<OrientationChangeCallback> mListeners = 56 new ArrayList<OrientationChangeCallback>(); 57 58 private static class OrientationChangeCallback { 59 private final Handler mHandler; 60 private final OnOrientationChangeListener mListener; 61 62 OrientationChangeCallback(Handler handler, OnOrientationChangeListener listener) { 63 mHandler = handler; 64 mListener = listener; 65 } 66 67 public void postOrientationChangeCallback(final int orientation) { 68 mHandler.post(new Runnable() { 69 @Override 70 public void run() { 71 mListener.onOrientationChanged(orientation); 72 } 73 }); 74 } 75 76 @Override 77 public boolean equals(Object o) { 78 if (o != null && o instanceof OrientationChangeCallback) { 79 OrientationChangeCallback c = (OrientationChangeCallback) o; 80 if (mHandler == c.mHandler && mListener == c.mListener) { 81 return true; 82 } 83 return false; 84 } 85 return false; 86 } 87 } 88 89 public OrientationManagerImpl(Activity activity) { 90 mActivity = activity; 91 mOrientationListener = new MyOrientationEventListener(activity); 92 } 93 94 public void resume() { 95 ContentResolver resolver = mActivity.getContentResolver(); 96 mRotationLockedSetting = Settings.System.getInt( 97 resolver, Settings.System.ACCELEROMETER_ROTATION, 0) != 1; 98 mOrientationListener.enable(); 99 } 100 101 public void pause() { 102 mOrientationListener.disable(); 103 } 104 105 //////////////////////////////////////////////////////////////////////////// 106 // Orientation handling 107 // 108 // We can choose to lock the framework orientation or not. If we lock the 109 // framework orientation, we calculate a a compensation value according to 110 // current device orientation and send it to listeners. If we don't lock 111 // the framework orientation, we always set the compensation value to 0. 112 //////////////////////////////////////////////////////////////////////////// 113 114 @Override 115 public void addOnOrientationChangeListener(Handler handler, 116 OnOrientationChangeListener listener) { 117 OrientationChangeCallback callback = new OrientationChangeCallback(handler, listener); 118 if (mListeners.contains(callback)) { 119 return; 120 } 121 mListeners.add(callback); 122 } 123 124 @Override 125 public void removeOnOrientationChangeListener(Handler handler, 126 OnOrientationChangeListener listener) { 127 OrientationChangeCallback callback = new OrientationChangeCallback(handler, listener); 128 if (!mListeners.remove(callback)) { 129 Log.v(TAG, "Removing non-existing listener."); 130 } 131 } 132 133 @Override 134 public void lockOrientation() { 135 if (mOrientationLocked || mRotationLockedSetting) { 136 return; 137 } 138 mOrientationLocked = true; 139 if (ApiHelper.HAS_ORIENTATION_LOCK) { 140 mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED); 141 } else { 142 mActivity.setRequestedOrientation(calculateCurrentScreenOrientation()); 143 } 144 } 145 146 @Override 147 public void unlockOrientation() { 148 if (!mOrientationLocked || mRotationLockedSetting) { 149 return; 150 } 151 mOrientationLocked = false; 152 Log.d(TAG, "unlock orientation"); 153 mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); 154 } 155 156 @Override 157 public boolean isOrientationLocked() { 158 return (mOrientationLocked || mRotationLockedSetting); 159 } 160 161 private int calculateCurrentScreenOrientation() { 162 int displayRotation = getDisplayRotation(); 163 // Display rotation >= 180 means we need to use the REVERSE landscape/portrait 164 boolean standard = displayRotation < 180; 165 if (mActivity.getResources().getConfiguration().orientation 166 == Configuration.ORIENTATION_LANDSCAPE) { 167 return standard 168 ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE 169 : ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; 170 } else { 171 if (displayRotation == 90 || displayRotation == 270) { 172 // If displayRotation = 90 or 270 then we are on a landscape 173 // device. On landscape devices, portrait is a 90 degree 174 // clockwise rotation from landscape, so we need 175 // to flip which portrait we pick as display rotation is counter clockwise 176 standard = !standard; 177 } 178 return standard 179 ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT 180 : ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; 181 } 182 } 183 184 // This listens to the device orientation, so we can update the compensation. 185 private class MyOrientationEventListener extends OrientationEventListener { 186 public MyOrientationEventListener(Context context) { 187 super(context); 188 } 189 190 @Override 191 public void onOrientationChanged(int orientation) { 192 // We keep the last known orientation. So if the user first orient 193 // the camera then point the camera to floor or sky, we still have 194 // the correct orientation. 195 if (orientation == ORIENTATION_UNKNOWN) { 196 return; 197 } 198 // TODO: We have two copies of the rounding method: one is CameraUtil.roundOrientation 199 // and the other is OrientationManagerImpl.roundOrientation. The same computation is 200 // done twice when orientation is changed. We should remove the duplicate. b/17440795 201 final int roundedOrientation = roundOrientation(orientation, 0); 202 for (OrientationChangeCallback l : mListeners) { 203 l.postOrientationChangeCallback(roundedOrientation); 204 } 205 } 206 } 207 208 @Override 209 public int getDisplayRotation() { 210 return getDisplayRotation(mActivity); 211 } 212 213 private static int roundOrientation(int orientation, int orientationHistory) { 214 boolean changeOrientation = false; 215 if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) { 216 changeOrientation = true; 217 } else { 218 int dist = Math.abs(orientation - orientationHistory); 219 dist = Math.min(dist, 360 - dist); 220 changeOrientation = (dist >= 45 + ORIENTATION_HYSTERESIS); 221 } 222 if (changeOrientation) { 223 return ((orientation + 45) / 90 * 90) % 360; 224 } 225 return orientationHistory; 226 } 227 228 private static int getDisplayRotation(Activity activity) { 229 int rotation = activity.getWindowManager().getDefaultDisplay() 230 .getRotation(); 231 switch (rotation) { 232 case Surface.ROTATION_0: return 0; 233 case Surface.ROTATION_90: return 90; 234 case Surface.ROTATION_180: return 180; 235 case Surface.ROTATION_270: return 270; 236 } 237 return 0; 238 } 239 } 240